{
  "openapi": "3.1.0",
  "info": {
    "title": "Foodashi API",
    "description": "Comprehensive food and recipe API with real calculated nutrition from USDA/EU ingredient databases. Features 70+ endpoints for recipe discovery, ingredient analysis, AI meal planning, food image recognition, and dietary filtering. Includes region/continent/country geo-filters and a unified multi-filter endpoint for complex queries.",
    "version": "1.0.0",
    "contact": {
      "name": "Foodashi Support",
      "url": "https://foodashi.com",
      "email": "support@foodashi.com"
    },
    "termsOfService": "https://foodashi.com/terms-of-service/",
    "license": {
      "name": "Proprietary",
      "url": "https://foodashi.com/api-license-agreement/"
    }
  },
  "servers": [
    {
      "url": "https://api.foodashi.com",
      "description": "Production"
    }
  ],
  "security": [
    {
      "ApiKeyAuth": []
    }
  ],
  "tags": [
    {
      "name": "Recipes",
      "description": "Browse, search, and retrieve recipes with full nutrition data"
    },
    {
      "name": "Recipe Browse",
      "description": "Lightweight recipe card browsing by various filters (cuisine, diet, time, etc.)"
    },
    {
      "name": "Recipe Sub-Resources",
      "description": "Focused data endpoints for individual recipe nutrition, taste, allergens, scaling, and more"
    },
    {
      "name": "Ingredients",
      "description": "Search, browse, and get detailed nutrition for canonical ingredients"
    },
    {
      "name": "Ingredient Browse",
      "description": "Browse ingredients by food group, nutrient range, and compare side-by-side"
    },
    {
      "name": "Nutrition",
      "description": "Nutrient reference data, FDA/EU nutrition labels, and NutriMetric scoring"
    },
    {
      "name": "AI Features",
      "description": "AI-powered meal planning, food recognition, recipe analysis, and beverage pairing"
    },
    {
      "name": "Shopping Lists",
      "description": "Generate and optimize shopping lists from recipes or meal plans"
    },
    {
      "name": "AI Tips",
      "description": "AI-powered cooking and preparation tips for ingredients and recipes"
    },
    {
      "name": "Utilities",
      "description": "Unit conversion, allergen lists, dietary tags, and cuisine reference data"
    }
  ],
  "paths": {
    "/api/recipes": {
      "get": {
        "operationId": "getRecipes",
        "summary": "List recipes",
        "description": "Browse published recipes with optional filtering by cuisine, diet, allergen exclusion, and text search. Returns full recipe objects with computed nutrition.",
        "tags": ["Recipes"],
        "x-request-cost": 1,
        "x-tier-required": "hobby",
        "parameters": [
          {
            "name": "page",
            "in": "query",
            "description": "Page number (1-based)",
            "schema": { "type": "integer", "minimum": 1, "default": 1 }
          },
          {
            "name": "limit",
            "in": "query",
            "description": "Results per page",
            "schema": { "type": "integer", "minimum": 1, "maximum": 100, "default": 20 }
          },
          {
            "name": "cuisine",
            "in": "query",
            "description": "Filter by cuisine name (e.g., Italian, Japanese)",
            "schema": { "type": "string" }
          },
          {
            "name": "diet",
            "in": "query",
            "description": "Filter by dietary tag (e.g., Vegan, Gluten-Free)",
            "schema": { "type": "string" }
          },
          {
            "name": "allergen",
            "in": "query",
            "description": "Exclude recipes containing this allergen (EU 14 only: Gluten, Crustaceans, Eggs, Fish, Peanuts, Soybeans, Dairy, Tree Nuts, Celery, Mustard, Sesame, Sulfites, Lupin, Molluscs)",
            "schema": { "type": "string", "enum": ["Gluten", "Crustaceans", "Eggs", "Fish", "Peanuts", "Soybeans", "Dairy", "Tree Nuts", "Celery", "Mustard", "Sesame", "Sulfites", "Lupin", "Molluscs"] }
          },
          {
            "name": "q",
            "in": "query",
            "description": "Text search on recipe title",
            "schema": { "type": "string" }
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated list of recipes",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "recipes": {
                      "type": "array",
                      "items": { "$ref": "#/components/schemas/Recipe" }
                    },
                    "pagination": { "$ref": "#/components/schemas/Pagination" }
                  }
                }
              }
            }
          },
          "500": { "$ref": "#/components/responses/InternalError" }
        }
      }
    },
    "/api/recipes/details": {
      "get": {
        "operationId": "getRecipeDetails",
        "summary": "Get recipe details",
        "description": "Retrieve a single published recipe by ID with full nutrition, ingredients, steps, taste profile, and NutriMetric scoring.",
        "tags": ["Recipes"],
        "x-request-cost": 1,
        "x-tier-required": "hobby",
        "parameters": [
          {
            "name": "id",
            "in": "query",
            "required": true,
            "description": "Recipe UUID",
            "schema": { "type": "string", "format": "uuid" }
          }
        ],
        "responses": {
          "200": {
            "description": "Full recipe object",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Recipe" }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "500": { "$ref": "#/components/responses/InternalError" }
        }
      }
    },
    "/api/recipes/{id}": {
      "get": {
        "operationId": "getRecipeById",
        "summary": "Get recipe by ID (path param)",
        "description": "Retrieve a single published recipe by its UUID path segment. Returns the full recipe object (same shape as /api/recipes/details).",
        "tags": ["Recipes"],
        "x-request-cost": 1,
        "x-tier-required": "hobby",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Recipe UUID",
            "schema": { "type": "string", "format": "uuid" }
          }
        ],
        "responses": {
          "200": {
            "description": "Full recipe object",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Recipe" }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "500": { "$ref": "#/components/responses/InternalError" }
        }
      }
    },
    "/api/recipes/by-region": {
      "get": {
        "operationId": "browseRecipesByRegion",
        "summary": "Browse recipes by region",
        "description": "Get lightweight recipe cards filtered by culinary region (e.g., Southern Europe, Southeast Asia).",
        "tags": ["Recipe Browse"],
        "x-request-cost": 1,
        "x-tier-required": "hobby",
        "parameters": [
          {
            "name": "region",
            "in": "query",
            "required": true,
            "description": "Region name (e.g., Southern Europe, Southeast Asia, Caribbean)",
            "schema": { "type": "string" }
          },
          { "$ref": "#/components/parameters/Page" },
          { "$ref": "#/components/parameters/Limit" }
        ],
        "responses": {
          "200": { "$ref": "#/components/responses/RecipeCardList" },
          "400": { "$ref": "#/components/responses/BadRequest" }
        }
      }
    },
    "/api/recipes/by-continent": {
      "get": {
        "operationId": "browseRecipesByContinent",
        "summary": "Browse recipes by continent",
        "description": "Get lightweight recipe cards filtered by continent (e.g., Europe, Asia, Africa, North America).",
        "tags": ["Recipe Browse"],
        "x-request-cost": 1,
        "x-tier-required": "hobby",
        "parameters": [
          {
            "name": "continent",
            "in": "query",
            "required": true,
            "description": "Continent name (e.g., Europe, Asia, Africa, North America, South America)",
            "schema": { "type": "string" }
          },
          { "$ref": "#/components/parameters/Page" },
          { "$ref": "#/components/parameters/Limit" }
        ],
        "responses": {
          "200": { "$ref": "#/components/responses/RecipeCardList" },
          "400": { "$ref": "#/components/responses/BadRequest" }
        }
      }
    },
    "/api/recipes/by-country": {
      "get": {
        "operationId": "browseRecipesByCountry",
        "summary": "Browse recipes by country",
        "description": "Get lightweight recipe cards filtered by country (ISO 3166-1 alpha-2 code or country name).",
        "tags": ["Recipe Browse"],
        "x-request-cost": 1,
        "x-tier-required": "hobby",
        "parameters": [
          {
            "name": "country",
            "in": "query",
            "required": true,
            "description": "Country code (e.g., IT, JP, MX) or country name",
            "schema": { "type": "string" }
          },
          { "$ref": "#/components/parameters/Page" },
          { "$ref": "#/components/parameters/Limit" }
        ],
        "responses": {
          "200": { "$ref": "#/components/responses/RecipeCardList" },
          "400": { "$ref": "#/components/responses/BadRequest" }
        }
      }
    },
    "/api/recipes/filter": {
      "get": {
        "operationId": "filterRecipes",
        "summary": "Multi-filter recipe search",
        "description": "Combine 25+ optional filters in a single query — taxonomy (cuisine, region, country_code, meal_type, dish_type, diet, difficulty), nutrition ranges (min/max calories, protein, fat, carbs, fiber, sugar, sodium), NutriMetric grade (nutrimetric_min_grade), time (max_time_minutes, max_prep_minutes), allergens (exclude_allergens), tags (include_tags), has_image, servings, and sort/page. All filters resolve to indexed columns. The response echoes filters_applied so you can confirm which parameters were honoured.",
        "tags": ["Recipe Browse"],
        "x-request-cost": 1,
        "x-tier-required": "indie",
        "parameters": [
          { "name": "cuisine", "in": "query", "schema": { "type": "string" }, "description": "Filter by cuisine name" },
          { "name": "region", "in": "query", "schema": { "type": "string" }, "description": "Filter by culinary region" },
          { "name": "country_code", "in": "query", "schema": { "type": "string" }, "description": "Filter by ISO 3166-1 alpha-2 country code" },
          { "name": "meal_type", "in": "query", "schema": { "type": "string" }, "description": "Filter by meal type (matches any value in meal_types[])" },
          { "name": "dish_type", "in": "query", "schema": { "type": "string" }, "description": "Filter by dish type (matches any value in dish_types[])" },
          { "name": "diet", "in": "query", "schema": { "type": "string" }, "description": "Filter by dietary tag (e.g., Vegan, Keto)" },
          { "name": "difficulty", "in": "query", "schema": { "type": "string", "enum": ["Easy", "Medium", "Hard", "Expert"] }, "description": "Filter by difficulty" },
          { "name": "max_time_minutes", "in": "query", "schema": { "type": "integer" }, "description": "Maximum total time in minutes" },
          { "name": "max_prep_minutes", "in": "query", "schema": { "type": "integer" }, "description": "Maximum prep time in minutes" },
          { "name": "min_calories", "in": "query", "schema": { "type": "number" }, "description": "Minimum calories per serving" },
          { "name": "max_calories", "in": "query", "schema": { "type": "number" }, "description": "Maximum calories per serving" },
          { "name": "min_protein_g", "in": "query", "schema": { "type": "number" }, "description": "Minimum protein (g) per serving" },
          { "name": "max_protein_g", "in": "query", "schema": { "type": "number" }, "description": "Maximum protein (g) per serving" },
          { "name": "min_fat_g", "in": "query", "schema": { "type": "number" }, "description": "Minimum fat (g) per serving" },
          { "name": "max_fat_g", "in": "query", "schema": { "type": "number" }, "description": "Maximum fat (g) per serving" },
          { "name": "min_carbs_g", "in": "query", "schema": { "type": "number" }, "description": "Minimum carbohydrate (g) per serving" },
          { "name": "max_carbs_g", "in": "query", "schema": { "type": "number" }, "description": "Maximum carbohydrate (g) per serving" },
          { "name": "min_fiber_g", "in": "query", "schema": { "type": "number" }, "description": "Minimum fiber (g) per serving" },
          { "name": "max_sugar_g", "in": "query", "schema": { "type": "number" }, "description": "Maximum total sugars (g) per serving" },
          { "name": "max_sodium_mg", "in": "query", "schema": { "type": "number" }, "description": "Maximum sodium (mg) per serving" },
          { "name": "nutrimetric_min_grade", "in": "query", "schema": { "type": "string", "enum": ["A", "B", "C", "D", "E", "F"] }, "description": "Minimum NutriMetric grade" },
          { "name": "exclude_allergens", "in": "query", "schema": { "type": "string" }, "description": "Comma-separated EU-14 allergens to exclude (e.g., Eggs,Nuts)" },
          { "name": "include_tags", "in": "query", "schema": { "type": "string" }, "description": "Comma-separated tags to require (GIN array-contains, max 10)" },
          { "name": "has_image", "in": "query", "schema": { "type": "boolean" }, "description": "Only return recipes that have an image" },
          { "name": "servings", "in": "query", "schema": { "type": "integer" }, "description": "Filter by servings count" },
          { "name": "sort", "in": "query", "schema": { "type": "string", "enum": ["time_asc", "time_desc", "nutrimetric", "newest", "popular"] }, "description": "Sort order" },
          { "$ref": "#/components/parameters/Page" },
          { "$ref": "#/components/parameters/Limit" }
        ],
        "responses": {
          "200": { "$ref": "#/components/responses/RecipeCardList" },
          "400": { "$ref": "#/components/responses/BadRequest" }
        }
      }
    },
    "/api/recipes/analyze": {
      "post": {
        "operationId": "analyzeExistingRecipe",
        "summary": "Analyze an existing recipe by ID",
        "description": "Run deep analysis on a recipe already in the Foodashi database — returns its full classification (meal_types, dish_types, occasions), nutrition breakdown, allergen detection, dietary tag confidence, and NutriMetric score. Use this to enrich recipe metadata without re-running /api/analyze/text.",
        "tags": ["AI"],
        "x-request-cost": 5,
        "x-tier-required": "pro",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["recipe_id"],
                "properties": {
                  "recipe_id": { "type": "string", "format": "uuid", "description": "Recipe UUID from /api/recipes or /api/search" }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Recipe analysis result",
            "content": {
              "application/json": {
                "schema": { "type": "object" }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      }
    },
    "/api/ingredients/semantic-search": {
      "get": {
        "operationId": "semanticIngredientSearch",
        "summary": "Semantic ingredient search",
        "description": "AI-powered semantic search over the canonical ingredient catalog using vector embeddings and similarity, with nutrition attached. Understands queries like 'fish sauce alternative'.",
        "tags": ["Ingredients"],
        "x-request-cost": 5,
        "x-tier-required": "pro",
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "required": true,
            "description": "Natural language ingredient query",
            "schema": { "type": "string" }
          }
        ],
        "responses": {
          "200": {
            "description": "Semantic ingredient search results",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "results": { "type": "array", "items": { "type": "object" } },
                    "query": { "type": "string" }
                  }
                }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" }
        }
      }
    },
    "/api/ingredients/batch": {
      "post": {
        "operationId": "batchIngredientLookup",
        "summary": "Batch ingredient lookup",
        "description": "Look up multiple canonical ingredients (with nutrition) in a single request. Pass an array of ingredient UUIDs.",
        "tags": ["Ingredients"],
        "x-request-cost": 5,
        "x-tier-required": "pro",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["ids"],
                "properties": {
                  "ids": {
                    "type": "array",
                    "items": { "type": "string", "format": "uuid" },
                    "description": "Array of canonical ingredient UUIDs (up to 50)",
                    "maxItems": 50
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Batch ingredient results",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "results": { "type": "array", "items": { "type": "object" } }
                  }
                }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" }
        }
      }
    },
    "/api/autocomplete": {
      "get": {
        "operationId": "autocompleteRecipes",
        "summary": "Autocomplete recipe titles",
        "description": "Fast prefix-based autocomplete for recipe titles. Returns lightweight id+title pairs.",
        "tags": ["Recipes"],
        "x-request-cost": 1,
        "x-tier-required": "indie",
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "required": true,
            "description": "Search query (minimum 2 characters)",
            "schema": { "type": "string", "minLength": 2 }
          },
          {
            "name": "limit",
            "in": "query",
            "description": "Maximum results",
            "schema": { "type": "integer", "minimum": 1, "maximum": 10, "default": 5 }
          }
        ],
        "responses": {
          "200": {
            "description": "Autocomplete results",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "results": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": { "type": "string", "format": "uuid" },
                          "title": { "type": "string" }
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/bulk-lookup": {
      "get": {
        "operationId": "bulkLookupRecipes",
        "summary": "Bulk lookup recipes by IDs",
        "description": "Fetch up to 50 recipes by their IDs in a single request. Returns full recipe objects.",
        "tags": ["Recipes"],
        "x-request-cost": 2,
        "x-tier-required": "indie",
        "parameters": [
          {
            "name": "ids",
            "in": "query",
            "required": true,
            "description": "Comma-separated recipe UUIDs (max 50)",
            "schema": { "type": "string" },
            "example": "uuid1,uuid2,uuid3"
          }
        ],
        "responses": {
          "200": {
            "description": "Bulk recipe results",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "recipes": {
                      "type": "array",
                      "items": { "$ref": "#/components/schemas/Recipe" }
                    }
                  }
                }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "500": { "$ref": "#/components/responses/InternalError" }
        }
      }
    },
    "/api/similar": {
      "get": {
        "operationId": "getSimilarRecipes",
        "summary": "Find similar recipes",
        "description": "Find recipes similar to a given recipe based on cuisine and dish type.",
        "tags": ["Recipes"],
        "x-request-cost": 1,
        "x-tier-required": "indie",
        "parameters": [
          {
            "name": "id",
            "in": "query",
            "required": true,
            "description": "Source recipe UUID",
            "schema": { "type": "string", "format": "uuid" }
          },
          {
            "name": "limit",
            "in": "query",
            "description": "Maximum results",
            "schema": { "type": "integer", "minimum": 1, "maximum": 20, "default": 5 }
          }
        ],
        "responses": {
          "200": {
            "description": "Similar recipes",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "recipes": {
                      "type": "array",
                      "items": { "$ref": "#/components/schemas/Recipe" }
                    }
                  }
                }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "500": { "$ref": "#/components/responses/InternalError" }
        }
      }
    },
    "/api/random": {
      "get": {
        "operationId": "getRandomRecipe",
        "summary": "Get a random recipe",
        "description": "Return a single random published recipe, optionally filtered by cuisine.",
        "tags": ["Recipes"],
        "x-request-cost": 1,
        "x-tier-required": "hobby",
        "parameters": [
          {
            "name": "cuisine",
            "in": "query",
            "description": "Filter random selection by cuisine",
            "schema": { "type": "string" }
          }
        ],
        "responses": {
          "200": {
            "description": "A random recipe",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Recipe" }
              }
            }
          },
          "404": { "$ref": "#/components/responses/NotFound" },
          "500": { "$ref": "#/components/responses/InternalError" }
        }
      }
    },
    "/api/ingredient-substitute": {
      "get": {
        "operationId": "getIngredientSubstitute",
        "summary": "Find ingredient substitutes",
        "description": "Look up common substitutes for a cooking ingredient (dairy, eggs, flour, proteins, etc.).",
        "tags": ["Utilities"],
        "x-request-cost": 1,
        "x-tier-required": "hobby",
        "parameters": [
          {
            "name": "ingredient",
            "in": "query",
            "required": true,
            "description": "Ingredient name to find substitutes for",
            "schema": { "type": "string" },
            "example": "butter"
          }
        ],
        "responses": {
          "200": {
            "description": "Substitute suggestions",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "original": { "type": "string" },
                    "substitutes": { "type": "array", "items": { "type": "string" } },
                    "category": { "type": "string", "nullable": true },
                    "note": { "type": "string" }
                  }
                }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" }
        }
      }
    },
    "/api/search": {
      "get": {
        "operationId": "semanticRecipeSearch",
        "summary": "Semantic recipe search",
        "description": "AI-powered semantic search using vector embeddings and similarity. Understands natural language queries like 'quick healthy dinner for two'.",
        "tags": ["Recipes"],
        "x-request-cost": 5,
        "x-tier-required": "indie",
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "required": true,
            "description": "Natural language search query",
            "schema": { "type": "string" }
          },
          {
            "name": "limit",
            "in": "query",
            "description": "Maximum results",
            "schema": { "type": "integer", "minimum": 1, "maximum": 20, "default": 10 }
          }
        ],
        "responses": {
          "200": {
            "description": "Semantic search results",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "results": { "type": "array", "items": { "$ref": "#/components/schemas/RecipeCard" } },
                    "query": { "type": "string" }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/recipes/by-cuisine": {
      "get": {
        "operationId": "browseRecipesByCuisine",
        "summary": "Browse recipes by cuisine",
        "description": "Get lightweight recipe cards filtered by cuisine name.",
        "tags": ["Recipe Browse"],
        "x-request-cost": 1,
        "x-tier-required": "hobby",
        "parameters": [
          {
            "name": "cuisine",
            "in": "query",
            "required": true,
            "description": "Cuisine name (e.g., Italian, Japanese, Mexican)",
            "schema": { "type": "string" }
          },
          { "$ref": "#/components/parameters/Page" },
          { "$ref": "#/components/parameters/Limit" }
        ],
        "responses": {
          "200": { "$ref": "#/components/responses/RecipeCardList" },
          "400": { "$ref": "#/components/responses/BadRequest" }
        }
      }
    },
    "/api/recipes/by-diet": {
      "get": {
        "operationId": "browseRecipesByDiet",
        "summary": "Browse recipes by diet",
        "description": "Get recipe cards filtered by dietary tag (Vegan, Keto, Gluten-Free, etc.).",
        "tags": ["Recipe Browse"],
        "x-request-cost": 1,
        "x-tier-required": "hobby",
        "parameters": [
          {
            "name": "diet",
            "in": "query",
            "required": true,
            "description": "Dietary tag (e.g., Vegan, Vegetarian, Keto, Gluten-Free)",
            "schema": { "type": "string" }
          },
          { "$ref": "#/components/parameters/Page" },
          { "$ref": "#/components/parameters/Limit" }
        ],
        "responses": {
          "200": { "$ref": "#/components/responses/RecipeCardList" },
          "400": { "$ref": "#/components/responses/BadRequest" }
        }
      }
    },
    "/api/recipes/by-meal-type": {
      "get": {
        "operationId": "browseRecipesByMealType",
        "summary": "Browse recipes by meal type",
        "description": "Get recipe cards filtered by meal type (breakfast, lunch, dinner, etc.).",
        "tags": ["Recipe Browse"],
        "x-request-cost": 1,
        "x-tier-required": "hobby",
        "parameters": [
          {
            "name": "type",
            "in": "query",
            "required": true,
            "description": "Meal type (e.g., breakfast, lunch, dinner, dessert, snack)",
            "schema": { "type": "string" }
          },
          { "$ref": "#/components/parameters/Page" },
          { "$ref": "#/components/parameters/Limit" }
        ],
        "responses": {
          "200": { "$ref": "#/components/responses/RecipeCardList" },
          "400": { "$ref": "#/components/responses/BadRequest" }
        }
      }
    },
    "/api/recipes/by-time": {
      "get": {
        "operationId": "browseRecipesByTime",
        "summary": "Browse recipes by max cook time",
        "description": "Get recipe cards that can be prepared within a given time limit.",
        "tags": ["Recipe Browse"],
        "x-request-cost": 1,
        "x-tier-required": "hobby",
        "parameters": [
          {
            "name": "maxMinutes",
            "in": "query",
            "required": false,
            "description": "Maximum total time in minutes",
            "schema": { "type": "integer", "minimum": 1, "default": 30 }
          },
          { "$ref": "#/components/parameters/Page" },
          { "$ref": "#/components/parameters/Limit" }
        ],
        "responses": {
          "200": { "$ref": "#/components/responses/RecipeCardList" },
          "400": { "$ref": "#/components/responses/BadRequest" }
        }
      }
    },
    "/api/recipes/by-difficulty": {
      "get": {
        "operationId": "browseRecipesByDifficulty",
        "summary": "Browse recipes by difficulty",
        "description": "Get recipe cards filtered by difficulty level.",
        "tags": ["Recipe Browse"],
        "x-request-cost": 1,
        "x-tier-required": "hobby",
        "parameters": [
          {
            "name": "difficulty",
            "in": "query",
            "required": true,
            "description": "Difficulty level",
            "schema": { "type": "string", "enum": ["Easy", "Medium", "Hard", "Expert"] }
          },
          { "$ref": "#/components/parameters/Page" },
          { "$ref": "#/components/parameters/Limit" }
        ],
        "responses": {
          "200": { "$ref": "#/components/responses/RecipeCardList" },
          "400": { "$ref": "#/components/responses/BadRequest" }
        }
      }
    },
    "/api/recipes/by-cooking-method": {
      "get": {
        "operationId": "browseRecipesByCookingMethod",
        "summary": "Browse recipes by cooking method",
        "description": "Get recipe cards filtered by cooking method (grilling, baking, frying, etc.).",
        "tags": ["Recipe Browse"],
        "x-request-cost": 1,
        "x-tier-required": "indie",
        "parameters": [
          {
            "name": "method",
            "in": "query",
            "required": true,
            "description": "Cooking method (e.g., grilling, baking, frying, steaming, roasting)",
            "schema": { "type": "string" }
          },
          { "$ref": "#/components/parameters/Page" },
          { "$ref": "#/components/parameters/Limit" }
        ],
        "responses": {
          "200": { "$ref": "#/components/responses/RecipeCardList" },
          "400": { "$ref": "#/components/responses/BadRequest" }
        }
      }
    },
    "/api/recipes/by-equipment": {
      "get": {
        "operationId": "browseRecipesByEquipment",
        "summary": "Browse recipes by equipment",
        "description": "Get recipe cards filtered by required kitchen equipment.",
        "tags": ["Recipe Browse"],
        "x-request-cost": 1,
        "x-tier-required": "indie",
        "parameters": [
          {
            "name": "equipment",
            "in": "query",
            "required": true,
            "description": "Equipment name (e.g., oven, blender, wok, grill)",
            "schema": { "type": "string" }
          },
          { "$ref": "#/components/parameters/Page" },
          { "$ref": "#/components/parameters/Limit" }
        ],
        "responses": {
          "200": { "$ref": "#/components/responses/RecipeCardList" },
          "400": { "$ref": "#/components/responses/BadRequest" }
        }
      }
    },
    "/api/recipes/by-occasion": {
      "get": {
        "operationId": "browseRecipesByOccasion",
        "summary": "Browse recipes by occasion",
        "description": "Get recipe cards filtered by occasion (Christmas, Thanksgiving, etc.).",
        "tags": ["Recipe Browse"],
        "x-request-cost": 1,
        "x-tier-required": "indie",
        "parameters": [
          {
            "name": "occasion",
            "in": "query",
            "required": true,
            "description": "Occasion name (e.g., Christmas, Thanksgiving, Valentine's Day, Party)",
            "schema": { "type": "string" }
          },
          { "$ref": "#/components/parameters/Page" },
          { "$ref": "#/components/parameters/Limit" }
        ],
        "responses": {
          "200": { "$ref": "#/components/responses/RecipeCardList" },
          "400": { "$ref": "#/components/responses/BadRequest" }
        }
      }
    },
    "/api/recipes/by-season": {
      "get": {
        "operationId": "browseRecipesBySeason",
        "summary": "Browse recipes by season",
        "description": "Get recipe cards filtered by season. Supports Summer, Winter, Spring, Autumn, Year-Round.",
        "tags": ["Recipe Browse"],
        "x-request-cost": 1,
        "x-tier-required": "indie",
        "parameters": [
          {
            "name": "season",
            "in": "query",
            "required": true,
            "description": "Season name (Summer, Winter, Spring, Autumn/Fall, Year-Round)",
            "schema": { "type": "string" }
          },
          { "$ref": "#/components/parameters/Page" },
          { "$ref": "#/components/parameters/Limit" }
        ],
        "responses": {
          "200": { "$ref": "#/components/responses/RecipeCardList" },
          "400": { "$ref": "#/components/responses/BadRequest" }
        }
      }
    },
    "/api/recipes/by-nutrients": {
      "get": {
        "operationId": "browseRecipesByNutrients",
        "summary": "Browse recipes by nutrient range",
        "description": "Filter recipes by nutritional values per serving (min/max for calories, protein, fat, carbs, fiber, sodium).",
        "tags": ["Recipe Browse"],
        "x-request-cost": 1,
        "x-tier-required": "pro",
        "parameters": [
          { "name": "minCalories", "in": "query", "schema": { "type": "number" }, "description": "Minimum calories per serving" },
          { "name": "maxCalories", "in": "query", "schema": { "type": "number" }, "description": "Maximum calories per serving" },
          { "name": "minProtein", "in": "query", "schema": { "type": "number" }, "description": "Minimum protein (g) per serving" },
          { "name": "maxProtein", "in": "query", "schema": { "type": "number" }, "description": "Maximum protein (g) per serving" },
          { "name": "minFat", "in": "query", "schema": { "type": "number" }, "description": "Minimum fat (g) per serving" },
          { "name": "maxFat", "in": "query", "schema": { "type": "number" }, "description": "Maximum fat (g) per serving" },
          { "name": "minCarbs", "in": "query", "schema": { "type": "number" }, "description": "Minimum carbs (g) per serving" },
          { "name": "maxCarbs", "in": "query", "schema": { "type": "number" }, "description": "Maximum carbs (g) per serving" },
          { "name": "minFiber", "in": "query", "schema": { "type": "number" }, "description": "Minimum fiber (g) per serving" },
          { "name": "maxFiber", "in": "query", "schema": { "type": "number" }, "description": "Maximum fiber (g) per serving" },
          { "name": "minSodium", "in": "query", "schema": { "type": "number" }, "description": "Minimum sodium (mg) per serving" },
          { "name": "maxSodium", "in": "query", "schema": { "type": "number" }, "description": "Maximum sodium (mg) per serving" },
          { "$ref": "#/components/parameters/Page" },
          { "$ref": "#/components/parameters/Limit" }
        ],
        "responses": {
          "200": { "$ref": "#/components/responses/RecipeCardList" },
          "400": { "$ref": "#/components/responses/BadRequest" }
        }
      }
    },
    "/api/recipes/by-ingredients": {
      "get": {
        "operationId": "browseRecipesByIngredients",
        "summary": "Browse recipes by ingredients",
        "description": "Find recipes containing all specified include-ingredients and none of the exclude-ingredients. Max 10 per list.",
        "tags": ["Recipe Browse"],
        "x-request-cost": 2,
        "x-tier-required": "indie",
        "parameters": [
          {
            "name": "include",
            "in": "query",
            "description": "Comma-separated ingredient names to include (e.g., chicken,rice)",
            "schema": { "type": "string" }
          },
          {
            "name": "exclude",
            "in": "query",
            "description": "Comma-separated ingredient names to exclude (e.g., nuts,shellfish)",
            "schema": { "type": "string" }
          },
          { "$ref": "#/components/parameters/Page" },
          { "$ref": "#/components/parameters/Limit" }
        ],
        "responses": {
          "200": { "$ref": "#/components/responses/RecipeCardList" },
          "400": { "$ref": "#/components/responses/BadRequest" }
        }
      }
    },
    "/api/recipes/count": {
      "get": {
        "operationId": "getRecipeCount",
        "summary": "Count recipes",
        "description": "Get the total count of published recipes matching optional filters.",
        "tags": ["Recipe Browse"],
        "x-request-cost": 1,
        "x-tier-required": "hobby",
        "parameters": [
          { "name": "cuisine", "in": "query", "schema": { "type": "string" }, "description": "Filter by cuisine" },
          { "name": "diet", "in": "query", "schema": { "type": "string" }, "description": "Filter by dietary tag" },
          { "name": "difficulty", "in": "query", "schema": { "type": "string" }, "description": "Filter by difficulty" },
          { "name": "meal_type", "in": "query", "schema": { "type": "string" }, "description": "Filter by meal type" }
        ],
        "responses": {
          "200": {
            "description": "Recipe count",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "count": { "type": "integer" },
                    "filters": { "type": "object" }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/recipes/cuisines": {
      "get": {
        "operationId": "listRecipeCuisines",
        "summary": "List cuisines with recipe counts",
        "description": "Get all cuisines that have published recipes, with the count for each.",
        "tags": ["Recipe Browse"],
        "x-request-cost": 1,
        "x-tier-required": "hobby",
        "responses": {
          "200": {
            "description": "Cuisine list with counts",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "cuisines": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "name": { "type": "string" },
                          "count": { "type": "integer" }
                        }
                      }
                    },
                    "total": { "type": "integer" }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/recipes/tags": {
      "get": {
        "operationId": "listRecipeTags",
        "summary": "List tags with recipe counts",
        "description": "Get all tags used across published recipes with their counts.",
        "tags": ["Recipe Browse"],
        "x-request-cost": 1,
        "x-tier-required": "hobby",
        "responses": {
          "200": {
            "description": "Tag list with counts",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "tags": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "name": { "type": "string" },
                          "count": { "type": "integer" }
                        }
                      }
                    },
                    "total": { "type": "integer" }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/recipes/diets": {
      "get": {
        "operationId": "listRecipeDiets",
        "summary": "List diets with recipe counts",
        "description": "Get all dietary tags used across published recipes with their counts.",
        "tags": ["Recipe Browse"],
        "x-request-cost": 1,
        "x-tier-required": "hobby",
        "responses": {
          "200": {
            "description": "Diet list with counts",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "diets": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "name": { "type": "string" },
                          "count": { "type": "integer" }
                        }
                      }
                    },
                    "total": { "type": "integer" }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/recipes/compare": {
      "get": {
        "operationId": "compareRecipes",
        "summary": "Compare recipes side-by-side",
        "description": "Compare 2-5 recipes side by side with nutrition, allergens, dietary tags, and NutriMetric scores.",
        "tags": ["Recipe Sub-Resources"],
        "x-request-cost": 1,
        "x-tier-required": "pro",
        "parameters": [
          {
            "name": "ids",
            "in": "query",
            "required": true,
            "description": "Comma-separated recipe UUIDs (2-5)",
            "schema": { "type": "string" }
          }
        ],
        "responses": {
          "200": {
            "description": "Recipe comparison",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "comparison": { "type": "array", "items": { "type": "object" } },
                    "count": { "type": "integer" }
                  }
                }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      }
    },
    "/api/recipes/{id}/nutrition": {
      "get": {
        "operationId": "getRecipeNutrition",
        "summary": "Get recipe nutrition breakdown",
        "description": "Detailed per-serving nutrition with ingredient-level breakdown, confidence scores, and coverage metadata.",
        "tags": ["Recipe Sub-Resources"],
        "x-request-cost": 2,
        "x-tier-required": "indie",
        "parameters": [
          { "$ref": "#/components/parameters/RecipeId" }
        ],
        "responses": {
          "200": {
            "description": "Nutrition breakdown",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/RecipeNutritionResponse" }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      }
    },
    "/api/recipes/{id}/taste": {
      "get": {
        "operationId": "getRecipeTaste",
        "summary": "Get recipe taste profile",
        "description": "Returns a 6-axis taste profile (spicy, salty, sweet, umami, bitter, sour) on a 0-10 scale.",
        "tags": ["Recipe Sub-Resources"],
        "x-request-cost": 1,
        "x-tier-required": "pro",
        "parameters": [
          { "$ref": "#/components/parameters/RecipeId" }
        ],
        "responses": {
          "200": {
            "description": "Taste profile",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": { "type": "string", "format": "uuid" },
                    "title": { "type": "string" },
                    "taste_profile": { "$ref": "#/components/schemas/TasteProfile" }
                  }
                }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      }
    },
    "/api/recipes/{id}/allergens": {
      "get": {
        "operationId": "getRecipeAllergens",
        "summary": "Get recipe allergen declaration",
        "description": "EU 14 allergen compliance data with sources and warnings.",
        "tags": ["Recipe Sub-Resources"],
        "x-request-cost": 1,
        "x-tier-required": "indie",
        "parameters": [
          { "$ref": "#/components/parameters/RecipeId" }
        ],
        "responses": {
          "200": {
            "description": "Allergen declaration",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": { "type": "string", "format": "uuid" },
                    "title": { "type": "string" },
                    "allergens": { "type": "array", "items": { "type": "string" } },
                    "allergen_sources": { "type": "object" },
                    "allergen_warnings": { "type": "array", "items": { "type": "string" } },
                    "allergen_declaration": { "type": "object", "nullable": true },
                    "compliance": { "type": "object" }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/recipes/{id}/sustainability": {
      "get": {
        "operationId": "getRecipeSustainability",
        "summary": "Get recipe sustainability metrics",
        "description": "NutriMetric scoring and dietary analysis for sustainability assessment.",
        "tags": ["Recipe Sub-Resources"],
        "x-request-cost": 1,
        "x-tier-required": "pro",
        "parameters": [
          { "$ref": "#/components/parameters/RecipeId" }
        ],
        "responses": {
          "200": {
            "description": "Sustainability data",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": { "type": "string", "format": "uuid" },
                    "title": { "type": "string" },
                    "sustainability": { "type": "object" }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/recipes/{id}/beverages": {
      "get": {
        "operationId": "getRecipeBeverages",
        "summary": "Get recipe beverage pairing",
        "description": "AI-generated beverage pairing suggestions stored on the recipe.",
        "tags": ["Recipe Sub-Resources"],
        "x-request-cost": 1,
        "x-tier-required": "indie",
        "parameters": [
          { "$ref": "#/components/parameters/RecipeId" }
        ],
        "responses": {
          "200": {
            "description": "Beverage pairing",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": { "type": "string", "format": "uuid" },
                    "title": { "type": "string" },
                    "beverage_pairing": { "type": "object" },
                    "note": { "type": "string" }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/recipes/{id}/medical": {
      "get": {
        "operationId": "getRecipeMedical",
        "summary": "Get recipe medical cautions",
        "description": "Allergen data and dietary information relevant for medical/dietary considerations.",
        "tags": ["Recipe Sub-Resources"],
        "x-request-cost": 1,
        "x-tier-required": "pro",
        "parameters": [
          { "$ref": "#/components/parameters/RecipeId" }
        ],
        "responses": {
          "200": {
            "description": "Medical caution data",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": { "type": "string", "format": "uuid" },
                    "title": { "type": "string" },
                    "allergens": { "type": "array", "items": { "type": "string" } },
                    "allergen_warnings": { "type": "array", "items": { "type": "string" } },
                    "allergen_declaration": { "type": "object", "nullable": true },
                    "dietary_tags": { "type": "array", "items": { "type": "string" } },
                    "disclaimer": { "type": "string" }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/recipes/{id}/scale": {
      "get": {
        "operationId": "scaleRecipeServings",
        "summary": "Scale recipe servings",
        "description": "Recalculate ingredient quantities for a different number of servings (1-100).",
        "tags": ["Recipe Sub-Resources"],
        "x-request-cost": 1,
        "x-tier-required": "indie",
        "parameters": [
          { "$ref": "#/components/parameters/RecipeId" },
          {
            "name": "servings",
            "in": "query",
            "required": true,
            "description": "Target number of servings (1-100)",
            "schema": { "type": "integer", "minimum": 1, "maximum": 100 }
          }
        ],
        "responses": {
          "200": {
            "description": "Scaled ingredients",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": { "type": "string", "format": "uuid" },
                    "title": { "type": "string" },
                    "original_servings": { "type": "integer" },
                    "target_servings": { "type": "integer" },
                    "scale_ratio": { "type": "number" },
                    "ingredients": { "type": "array", "items": { "$ref": "#/components/schemas/ScaledIngredient" } }
                  }
                }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      }
    },
    "/api/ingredients/search": {
      "get": {
        "operationId": "searchIngredients",
        "summary": "Search ingredients",
        "description": "Search canonical ingredients by name with relevance scoring, nutrition data, and available portions. Supports filtering by type (raw, branded, cooked, processed).",
        "tags": ["Ingredients"],
        "x-request-cost": 1,
        "x-tier-required": "hobby",
        "parameters": [
          { "name": "q", "in": "query", "required": true, "description": "Search query", "schema": { "type": "string" } },
          { "name": "limit", "in": "query", "description": "Max results", "schema": { "type": "integer", "minimum": 1, "maximum": 50, "default": 20 } },
          { "name": "offset", "in": "query", "description": "Results offset for pagination", "schema": { "type": "integer", "minimum": 0, "default": 0 } },
          { "name": "type", "in": "query", "description": "Filter by ingredient type", "schema": { "type": "string", "enum": ["raw", "branded", "cooked", "processed", "all"], "default": "all" } },
          { "name": "require_nutrition", "in": "query", "description": "Only return ingredients with nutrition data", "schema": { "type": "string", "enum": ["true", "false"], "default": "false" } },
          { "name": "complete", "in": "query", "description": "Only return ingredients with complete NutriMetric data", "schema": { "type": "string", "enum": ["true", "false"], "default": "false" } }
        ],
        "responses": {
          "200": {
            "description": "Ingredient search results",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "results": { "type": "array", "items": { "$ref": "#/components/schemas/IngredientSearchResult" } },
                    "total": { "type": "integer" },
                    "returned": { "type": "integer" },
                    "limit": { "type": "integer" },
                    "offset": { "type": "integer" },
                    "query": { "type": "string" },
                    "type": { "type": "string" },
                    "available_types": { "type": "array", "items": { "type": "string" } }
                  }
                }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" }
        }
      }
    },
    "/api/ingredients/{id}": {
      "get": {
        "operationId": "getIngredientDetails",
        "summary": "Get ingredient details",
        "description": "Full ingredient profile with detailed nutrition by group, portions, and metadata.",
        "tags": ["Ingredients"],
        "x-request-cost": 1,
        "x-tier-required": "indie",
        "parameters": [
          { "$ref": "#/components/parameters/IngredientId" }
        ],
        "responses": {
          "200": {
            "description": "Full ingredient details",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/IngredientDetail" }
              }
            }
          },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      }
    },
    "/api/ingredients/{id}/convert": {
      "get": {
        "operationId": "convertIngredientUnit",
        "summary": "Convert ingredient units",
        "description": "Convert between measurement units for a specific ingredient using portion data from USDA.",
        "tags": ["Ingredients"],
        "x-request-cost": 1,
        "x-tier-required": "indie",
        "parameters": [
          { "$ref": "#/components/parameters/IngredientId" },
          { "name": "amount", "in": "query", "required": true, "description": "Amount to convert", "schema": { "type": "number", "minimum": 0.01, "maximum": 1000000 } },
          { "name": "from_unit", "in": "query", "required": true, "description": "Source unit", "schema": { "type": "string" } },
          { "name": "to_unit", "in": "query", "description": "Target unit (default: grams)", "schema": { "type": "string", "default": "grams" } }
        ],
        "responses": {
          "200": {
            "description": "Conversion result",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ingredient": { "type": "object" },
                    "conversion": {
                      "type": "object",
                      "properties": {
                        "from": { "type": "object" },
                        "to": { "type": "object" }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      }
    },
    "/api/ingredients/autocomplete": {
      "get": {
        "operationId": "autocompleteIngredients",
        "summary": "Autocomplete ingredient names",
        "description": "Fast prefix-based autocomplete for ingredient names. Returns lightweight results.",
        "tags": ["Ingredient Browse"],
        "x-request-cost": 1,
        "x-tier-required": "hobby",
        "parameters": [
          { "name": "q", "in": "query", "required": true, "description": "Search query (min 2 chars)", "schema": { "type": "string", "minLength": 2 } },
          { "name": "limit", "in": "query", "description": "Max results", "schema": { "type": "integer", "minimum": 1, "maximum": 20, "default": 10 } }
        ],
        "responses": {
          "200": {
            "description": "Autocomplete results",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "results": { "type": "array", "items": { "type": "object" } },
                    "query": { "type": "string" },
                    "count": { "type": "integer" }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/ingredients/{id}/recipes": {
      "get": {
        "operationId": "getIngredientRecipes",
        "summary": "Get recipes using ingredient",
        "description": "Find published recipes that use a specific canonical ingredient.",
        "tags": ["Ingredient Browse"],
        "x-request-cost": 1,
        "x-tier-required": "indie",
        "parameters": [
          { "$ref": "#/components/parameters/IngredientId" },
          { "$ref": "#/components/parameters/Page" },
          { "$ref": "#/components/parameters/Limit" }
        ],
        "responses": {
          "200": {
            "description": "Recipes using ingredient",
            "content": { "application/json": { "schema": { "type": "object" } } }
          }
        }
      }
    },
    "/api/ingredients/{id}/allergens": {
      "get": {
        "operationId": "getIngredientAllergens",
        "summary": "Get ingredient allergen info",
        "description": "Allergen detection for a specific ingredient based on EU 14 allergens.",
        "tags": ["Ingredient Browse"],
        "x-request-cost": 1,
        "x-tier-required": "indie",
        "parameters": [
          { "$ref": "#/components/parameters/IngredientId" }
        ],
        "responses": {
          "200": {
            "description": "Allergen info",
            "content": { "application/json": { "schema": { "type": "object" } } }
          },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      }
    },
    "/api/ingredients/{id}/portions": {
      "get": {
        "operationId": "getIngredientPortions",
        "summary": "Get ingredient portions",
        "description": "List available portion sizes with gram weights for a specific ingredient.",
        "tags": ["Ingredient Browse"],
        "x-request-cost": 1,
        "x-tier-required": "indie",
        "parameters": [
          { "$ref": "#/components/parameters/IngredientId" }
        ],
        "responses": {
          "200": {
            "description": "Portion data",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": { "type": "string", "format": "uuid" },
                    "name": { "type": "string" },
                    "default_unit": { "type": "string", "nullable": true },
                    "default_gram_weight": { "type": "number", "nullable": true },
                    "portions": { "type": "array", "items": { "type": "object", "properties": { "unit": { "type": "string" }, "gram_weight": { "type": "number" } } } },
                    "total_portions": { "type": "integer" }
                  }
                }
              }
            }
          },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      }
    },
    "/api/ingredients/{id}/substitutes": {
      "get": {
        "operationId": "getIngredientSubstitutes",
        "summary": "Get ingredient substitutes",
        "description": "Find common cooking substitutes for a specific ingredient by its canonical ID.",
        "tags": ["Ingredient Browse"],
        "x-request-cost": 1,
        "x-tier-required": "indie",
        "parameters": [
          { "$ref": "#/components/parameters/IngredientId" }
        ],
        "responses": {
          "200": {
            "description": "Substitute suggestions",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ingredient": { "type": "string" },
                    "substitutes": { "type": "array", "items": { "type": "object", "properties": { "name": { "type": "string" }, "ratio": { "type": "string" }, "notes": { "type": "string" } } } }
                  }
                }
              }
            }
          },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      }
    },
    "/api/ingredients/by-food-group": {
      "get": {
        "operationId": "browseIngredientsByFoodGroup",
        "summary": "Browse ingredients by food group",
        "description": "Get ingredients filtered by USDA food group (dairy, produce, protein, etc.).",
        "tags": ["Ingredient Browse"],
        "x-request-cost": 1,
        "x-tier-required": "indie",
        "parameters": [
          { "name": "group", "in": "query", "required": true, "description": "USDA food group ID or name", "schema": { "type": "string" } },
          { "$ref": "#/components/parameters/Page" },
          { "$ref": "#/components/parameters/Limit" }
        ],
        "responses": {
          "200": { "description": "Ingredients in food group", "content": { "application/json": { "schema": { "type": "object" } } } },
          "400": { "$ref": "#/components/responses/BadRequest" }
        }
      }
    },
    "/api/ingredients/by-nutrient": {
      "get": {
        "operationId": "browseIngredientsByNutrient",
        "summary": "Browse ingredients by nutrient range",
        "description": "Find ingredients with high/low values of a specific nutrient per 100g. Includes daily value percentages.",
        "tags": ["Ingredient Browse"],
        "x-request-cost": 1,
        "x-tier-required": "pro",
        "parameters": [
          { "name": "nutrient", "in": "query", "required": true, "description": "Nutrient key or name (e.g., protein, iron, vitamin_c)", "schema": { "type": "string" } },
          { "name": "min", "in": "query", "description": "Minimum value per 100g", "schema": { "type": "number", "default": 0 } },
          { "name": "max", "in": "query", "description": "Maximum value per 100g", "schema": { "type": "number", "default": 999999 } },
          { "name": "filter", "in": "query", "description": "Data source filter", "schema": { "type": "string", "enum": ["ingredients", "branded", "all"], "default": "ingredients" } },
          { "$ref": "#/components/parameters/Page" },
          { "$ref": "#/components/parameters/Limit" }
        ],
        "responses": {
          "200": { "description": "Ingredients ranked by nutrient value", "content": { "application/json": { "schema": { "type": "object" } } } },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      }
    },
    "/api/ingredients/food-groups": {
      "get": {
        "operationId": "listFoodGroups",
        "summary": "List food groups with counts",
        "description": "Get all USDA food groups with the count of ingredients in each.",
        "tags": ["Ingredient Browse"],
        "x-request-cost": 1,
        "x-tier-required": "hobby",
        "responses": {
          "200": {
            "description": "Food group list",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "food_groups": { "type": "array", "items": { "type": "object", "properties": { "id": { "type": "string" }, "name": { "type": "string" }, "count": { "type": "integer" } } } },
                    "total": { "type": "integer" }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/ingredients/compare": {
      "get": {
        "operationId": "compareIngredients",
        "summary": "Compare ingredients side-by-side",
        "description": "Compare 2-5 ingredients with full nutrition per 100g for each.",
        "tags": ["Ingredient Browse"],
        "x-request-cost": 2,
        "x-tier-required": "pro",
        "parameters": [
          {
            "name": "ids",
            "in": "query",
            "required": true,
            "description": "Comma-separated ingredient UUIDs (2-5)",
            "schema": { "type": "string" }
          }
        ],
        "responses": {
          "200": {
            "description": "Ingredient comparison",
            "content": { "application/json": { "schema": { "type": "object" } } }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      }
    },
    "/api/nutrition/nutrients": {
      "get": {
        "operationId": "listNutrients",
        "summary": "List all tracked nutrients",
        "description": "Get all nutrient definitions tracked in the database, grouped by category (Macros, Vitamins, Minerals, etc.).",
        "tags": ["Nutrition"],
        "x-request-cost": 1,
        "x-tier-required": "hobby",
        "responses": {
          "200": {
            "description": "Nutrient definitions",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "nutrients": { "type": "array", "items": { "$ref": "#/components/schemas/NutrientDef" } },
                    "grouped": { "type": "object" },
                    "total": { "type": "integer" }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/nutrition/daily-values": {
      "get": {
        "operationId": "getDailyValues",
        "summary": "Get daily reference values",
        "description": "FDA (2,000 calorie diet) + EU Reference Intake daily values for all tracked nutrients.",
        "tags": ["Nutrition"],
        "x-request-cost": 1,
        "x-tier-required": "hobby",
        "responses": {
          "200": {
            "description": "Daily reference values",
            "content": { "application/json": { "schema": { "type": "object" } } }
          }
        }
      }
    },
    "/api/nutrition/label": {
      "post": {
        "operationId": "generateNutritionLabel",
        "summary": "Generate nutrition label",
        "description": "Generate a structured FDA or EU-format nutrition label from per-serving nutrition data.",
        "tags": ["Nutrition"],
        "x-request-cost": 1,
        "x-tier-required": "pro",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["nutrition_per_serving"],
                "properties": {
                  "nutrition_per_serving": {
                    "type": "object",
                    "description": "Nutrition values per serving",
                    "properties": {
                      "calories": { "type": "number" },
                      "total_fat_g": { "type": "number" },
                      "saturated_fat_g": { "type": "number" },
                      "trans_fat_g": { "type": "number" },
                      "cholesterol_mg": { "type": "number" },
                      "sodium_mg": { "type": "number" },
                      "total_carbohydrate_g": { "type": "number" },
                      "dietary_fiber_g": { "type": "number" },
                      "total_sugars_g": { "type": "number" },
                      "added_sugars_g": { "type": "number" },
                      "protein_g": { "type": "number" }
                    }
                  },
                  "serving_size": { "type": "string", "description": "Human-readable serving size", "example": "2/3 cup (55g)" },
                  "servings_per_container": { "type": "integer", "description": "Servings per container" },
                  "format": { "type": "string", "enum": ["fda", "eu"], "default": "fda", "description": "Label format" }
                }
              }
            }
          }
        },
        "responses": {
          "200": { "description": "Formatted nutrition label", "content": { "application/json": { "schema": { "type": "object" } } } },
          "400": { "$ref": "#/components/responses/BadRequest" }
        }
      }
    },
    "/api/nutrition/nutrimetric": {
      "post": {
        "operationId": "calculateNutriMetric",
        "summary": "Calculate NutriMetric score",
        "description": "Calculate Foodashi's proprietary NutriMetric health score (A-F grade, 0-100 score) from per-100g nutrition data. Requires 5 core nutrients, supports 3 optional bonus nutrients.",
        "tags": ["Nutrition"],
        "x-request-cost": 1,
        "x-tier-required": "pro",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["nutrition_per_100g"],
                "properties": {
                  "nutrition_per_100g": {
                    "type": "object",
                    "description": "Nutrition values per 100g (5 required + 3 optional bonus fields)",
                    "required": ["energy_kcal", "protein_g", "fat_g", "carbohydrate_g", "sodium_mg"],
                    "properties": {
                      "energy_kcal": { "type": "number", "description": "Energy in kcal per 100g" },
                      "protein_g": { "type": "number", "description": "Protein in grams per 100g" },
                      "fat_g": { "type": "number", "description": "Total fat in grams per 100g" },
                      "carbohydrate_g": { "type": "number", "description": "Carbohydrate in grams per 100g" },
                      "sodium_mg": { "type": "number", "description": "Sodium in mg per 100g" },
                      "fiber_g": { "type": "number", "description": "Fiber in grams per 100g (bonus)" },
                      "saturated_fat_g": { "type": "number", "description": "Saturated fat in grams per 100g (bonus)" },
                      "sugars_total_g": { "type": "number", "description": "Total sugars in grams per 100g (bonus)" }
                    }
                  },
                  "category": { "type": "string", "enum": ["main", "beverage", "dessert", "soup", "salad", "snack"], "description": "Recipe category for scoring adjustment" }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "NutriMetric result",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "nutrimetric": { "$ref": "#/components/schemas/NutriMetric" },
                    "input": { "type": "object" },
                    "note": { "type": "string" },
                    "disclaimer": { "type": "string" }
                  }
                }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" }
        }
      }
    },
    "/api/recognize/image": {
      "post": {
        "operationId": "recognizeDish",
        "summary": "Identify dish from photo",
        "description": "AI-powered food photo analysis that identifies the dish, estimates nutrition, and matches to recipes in the database. Accepts base64 image or image URL.",
        "tags": ["AI Features"],
        "x-request-cost": 20,
        "x-tier-required": "pro",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "image": { "type": "string", "description": "Base64-encoded image (max 4MB). Supports data URI format." },
                  "image_url": { "type": "string", "format": "uri", "description": "URL to fetch image from (max 4MB). Supports JPEG, PNG, WebP, GIF." },
                  "include_recipes": { "type": "boolean", "default": true, "description": "Include matched recipes from database" },
                  "max_results": { "type": "integer", "minimum": 1, "maximum": 10, "default": 5, "description": "Maximum recipe matches to return" }
                }
              }
            }
          }
        },
        "responses": {
          "200": { "description": "Dish identification with matched recipes", "content": { "application/json": { "schema": { "type": "object" } } } },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "422": { "description": "Could not identify dish in image" }
        }
      }
    },
    "/api/recognize/ingredients": {
      "post": {
        "operationId": "recognizeIngredients",
        "summary": "Identify ingredients from photo",
        "description": "AI-powered ingredient photo analysis. Identifies ingredients, resolves to canonical database entries, and suggests recipes.",
        "tags": ["AI Features"],
        "x-request-cost": 25,
        "x-tier-required": "pro",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "image": { "type": "string", "description": "Base64-encoded image (max 4MB)" },
                  "image_url": { "type": "string", "format": "uri", "description": "URL to fetch image from (max 4MB)" },
                  "include_recipes": { "type": "boolean", "default": true, "description": "Include recipe suggestions" },
                  "max_results": { "type": "integer", "minimum": 1, "maximum": 10, "default": 5, "description": "Maximum recipe suggestions" }
                }
              }
            }
          }
        },
        "responses": {
          "200": { "description": "Ingredient identification with recipe suggestions", "content": { "application/json": { "schema": { "type": "object" } } } },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "422": { "description": "No ingredients detected in image" }
        }
      }
    },
    "/api/analyze/text": {
      "post": {
        "operationId": "analyzeRecipeText",
        "summary": "Analyze recipe from text",
        "description": "Paste raw recipe text for full AI analysis: ingredient resolution, real USDA/EU nutrition, allergen detection (EU 14), dietary tag calculation, and NutriMetric scoring.",
        "tags": ["AI Features"],
        "x-request-cost": 30,
        "x-tier-required": "pro",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["text"],
                "properties": {
                  "text": { "type": "string", "minLength": 20, "maxLength": 50000, "description": "Full recipe text with ingredients and instructions" },
                  "servings": { "type": "integer", "minimum": 1, "maximum": 100, "description": "Override serving count (auto-detected if omitted)" }
                }
              }
            }
          }
        },
        "responses": {
          "200": { "description": "Full recipe analysis", "content": { "application/json": { "schema": { "type": "object" } } } },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "422": { "description": "Text did not contain a recognizable recipe" }
        }
      }
    },
    "/api/analyze/url": {
      "post": {
        "operationId": "analyzeRecipeUrl",
        "summary": "Analyze recipe from URL",
        "description": "Provide a recipe page URL for full AI analysis. Fetches the page, extracts the recipe, then runs the complete analysis pipeline (nutrition, allergens, NutriMetric).",
        "tags": ["AI Features"],
        "x-request-cost": 35,
        "x-tier-required": "pro",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["url"],
                "properties": {
                  "url": { "type": "string", "format": "uri", "description": "URL of the recipe page to analyze" },
                  "servings": { "type": "integer", "minimum": 1, "maximum": 100, "description": "Override serving count" }
                }
              }
            }
          }
        },
        "responses": {
          "200": { "description": "Full recipe analysis", "content": { "application/json": { "schema": { "type": "object" } } } },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "422": { "description": "No recipe found at URL" },
          "502": { "description": "Failed to fetch URL" }
        }
      }
    },
    "/api/pairing/beverages": {
      "post": {
        "operationId": "getBeveragePairing",
        "summary": "AI beverage pairing",
        "description": "AI-powered sommelier that suggests wine, beer, cocktails, spirits, and non-alcoholic beverages for a dish. Accepts dish name, ingredient list, food photo, or recipe ID.",
        "tags": ["AI Features"],
        "x-request-cost": 15,
        "x-tier-required": "pro",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Provide exactly one of: dish, ingredients, image/image_url, or recipe_id",
                "properties": {
                  "dish": { "type": "string", "description": "Dish name (e.g., 'Pad Thai')" },
                  "ingredients": { "type": "array", "items": { "type": "string" }, "description": "Ingredient list to identify dish" },
                  "image": { "type": "string", "description": "Base64-encoded food photo" },
                  "image_url": { "type": "string", "format": "uri", "description": "URL to food photo" },
                  "recipe_id": { "type": "string", "format": "uuid", "description": "Recipe ID from database" },
                  "preferences": {
                    "type": "object",
                    "properties": {
                      "no_alcohol": { "type": "boolean", "description": "Only return non-alcoholic options" },
                      "budget": { "type": "string", "enum": ["low", "medium", "high"], "description": "Budget level for recommendations" },
                      "style": { "type": "string", "enum": ["casual", "fine-dining"], "description": "Dining context" }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Beverage pairing recommendations",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "dish": { "type": "string" },
                    "cuisine_detected": { "type": "string" },
                    "pairings": {
                      "type": "object",
                      "properties": {
                        "wine": { "type": "array", "items": { "type": "object" } },
                        "beer": { "type": "array", "items": { "type": "object" } },
                        "cocktails": { "type": "array", "items": { "type": "object" } },
                        "spirits": { "type": "array", "items": { "type": "object" } },
                        "non_alcoholic": { "type": "array", "items": { "type": "object" } }
                      }
                    },
                    "sommelier_note": { "type": "string" },
                    "confidence": { "type": "number" },
                    "input_type": { "type": "string" },
                    "processing_time_ms": { "type": "integer" }
                  }
                }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "503": { "description": "AI service temporarily unavailable" }
        }
      }
    },
    "/api/meal-plan/generate": {
      "post": {
        "operationId": "generateMealPlan",
        "summary": "Generate AI meal plan",
        "description": "AI-powered weekly meal plan generation with dietary restrictions, allergen exclusions, cuisine preferences, and calorie targets. Matches to real recipes in the database with full nutrition.",
        "tags": ["AI Features"],
        "x-request-cost": 75,
        "x-tier-required": "pro",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "days": { "type": "integer", "minimum": 1, "maximum": 14, "default": 7, "description": "Number of days to plan" },
                  "goal": { "type": "string", "enum": ["lose_weight", "maintain", "gain_weight", "build_muscle"], "default": "maintain", "description": "Nutritional goal" },
                  "calories_per_day": { "type": "integer", "minimum": 500, "maximum": 10000, "description": "Target daily calories (auto-set from goal if omitted)" },
                  "dietary_restrictions": { "type": "array", "items": { "type": "string" }, "description": "Dietary requirements (e.g., Vegan, Gluten-Free)" },
                  "allergen_exclusions": { "type": "array", "items": { "type": "string" }, "description": "Allergens to avoid (EU 14)" },
                  "cuisine_preferences": { "type": "array", "items": { "type": "string" }, "description": "Preferred cuisines" },
                  "cuisine_exclusions": { "type": "array", "items": { "type": "string" }, "description": "Cuisines to avoid" },
                  "max_prep_time_minutes": { "type": "integer", "description": "Max total time per recipe" },
                  "difficulty": { "type": "string", "enum": ["Easy", "Medium", "Hard", "Expert"], "description": "Recipe difficulty filter" },
                  "meals_per_day": { "type": "array", "items": { "type": "string", "enum": ["breakfast", "lunch", "dinner", "snack", "brunch"] }, "default": ["breakfast", "lunch", "dinner"], "description": "Meal types to plan" },
                  "servings": { "type": "integer", "minimum": 1, "maximum": 20, "default": 2, "description": "Servings per meal" },
                  "exclude_recipe_ids": { "type": "array", "items": { "type": "string", "format": "uuid" }, "description": "Recipe IDs to exclude" },
                  "include_snacks": { "type": "boolean", "default": false, "description": "Add snack slot to meals_per_day" }
                }
              }
            }
          }
        },
        "responses": {
          "200": { "description": "Generated meal plan with nutrition summary", "content": { "application/json": { "schema": { "type": "object" } } } },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "422": { "description": "AI failed to generate valid plan" }
        }
      }
    },
    "/api/meal-plan/customize": {
      "post": {
        "operationId": "customizeMealPlan",
        "summary": "Customize meal plan",
        "description": "Swap meals or regenerate a day in an existing meal plan. Supports swap (by recipe ID), regenerate_day, and regenerate_meal actions.",
        "tags": ["AI Features"],
        "x-request-cost": 40,
        "x-tier-required": "pro",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["plan", "action", "day_index"],
                "properties": {
                  "plan": { "type": "object", "description": "The existing plan object from /meal-plan/generate response" },
                  "action": { "type": "string", "enum": ["swap", "regenerate_day", "regenerate_meal"], "description": "Type of customization" },
                  "day_index": { "type": "integer", "minimum": 0, "description": "0-based day index to modify" },
                  "meal_index": { "type": "integer", "minimum": 0, "description": "0-based meal index (required for swap and regenerate_meal)" },
                  "swap_recipe_id": { "type": "string", "format": "uuid", "description": "Recipe ID to swap in (required for swap action)" },
                  "reason": { "type": "string", "maxLength": 200, "description": "Reason for change (helps AI pick better replacement)" }
                }
              }
            }
          }
        },
        "responses": {
          "200": { "description": "Updated meal plan", "content": { "application/json": { "schema": { "type": "object" } } } },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "422": { "description": "AI could not generate replacement" }
        }
      }
    },
    "/api/meal-plan/templates": {
      "get": {
        "operationId": "getMealPlanTemplates",
        "summary": "Get meal plan templates",
        "description": "Browse pre-built meal plan templates organized by goal (weight loss, muscle building, plant-based, etc.).",
        "tags": ["AI Features"],
        "x-request-cost": 1,
        "x-tier-required": "indie",
        "parameters": [
          {
            "name": "goal",
            "in": "query",
            "description": "Filter templates by goal",
            "schema": { "type": "string", "enum": ["lose_weight", "maintain", "gain_weight", "build_muscle"] }
          }
        ],
        "responses": {
          "200": {
            "description": "Meal plan templates",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": { "type": "boolean" },
                    "data": {
                      "type": "object",
                      "properties": {
                        "templates": { "type": "array", "items": { "type": "object" } },
                        "available_goals": { "type": "array", "items": { "type": "string" } }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/shopping-list/from-recipes": {
      "post": {
        "operationId": "shoppingListFromRecipes",
        "summary": "Generate shopping list from recipes",
        "description": "Aggregate ingredients from multiple recipes into an organized shopping list grouped by store section (Produce, Protein, Dairy, etc.). Supports serving overrides and metric/imperial units.",
        "tags": ["Shopping Lists"],
        "x-request-cost": 2,
        "x-tier-required": "indie",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["recipe_ids"],
                "properties": {
                  "recipe_ids": { "type": "array", "items": { "type": "string", "format": "uuid" }, "minItems": 1, "maxItems": 50, "description": "Recipe UUIDs to include" },
                  "servings_override": { "type": "object", "additionalProperties": { "type": "integer" }, "description": "Map of recipe_id to custom serving count" },
                  "unit_system": { "type": "string", "enum": ["metric", "imperial"], "default": "metric", "description": "Measurement unit system" }
                }
              }
            }
          }
        },
        "responses": {
          "200": { "description": "Shopping list organized by store section", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ShoppingListResponse" } } } },
          "400": { "$ref": "#/components/responses/BadRequest" }
        }
      }
    },
    "/api/shopping-list/from-meal-plan": {
      "post": {
        "operationId": "shoppingListFromMealPlan",
        "summary": "Generate shopping list from meal plan",
        "description": "Extract recipe IDs from a meal plan structure and generate a consolidated shopping list.",
        "tags": ["Shopping Lists"],
        "x-request-cost": 2,
        "x-tier-required": "pro",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["meal_plan"],
                "properties": {
                  "meal_plan": {
                    "type": "object",
                    "required": ["days"],
                    "properties": {
                      "days": {
                        "type": "array",
                        "items": {
                          "type": "object",
                          "properties": {
                            "meals": {
                              "type": "array",
                              "items": {
                                "type": "object",
                                "properties": {
                                  "recipe_id": { "type": "string", "format": "uuid" },
                                  "servings": { "type": "integer" }
                                }
                              }
                            }
                          }
                        }
                      }
                    }
                  },
                  "unit_system": { "type": "string", "enum": ["metric", "imperial"], "default": "metric" }
                }
              }
            }
          }
        },
        "responses": {
          "200": { "description": "Shopping list with meal plan summary", "content": { "application/json": { "schema": { "type": "object" } } } },
          "400": { "$ref": "#/components/responses/BadRequest" }
        }
      }
    },
    "/api/shopping-list/optimize": {
      "post": {
        "operationId": "optimizeShoppingList",
        "summary": "AI-optimize shopping list",
        "description": "AI-powered shopping list optimization with consolidation, substitution, and budget suggestions. Provide recipe_ids or meal_plan, plus optional preferences.",
        "tags": ["Shopping Lists"],
        "x-request-cost": 30,
        "x-tier-required": "pro",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "recipe_ids": { "type": "array", "items": { "type": "string", "format": "uuid" }, "description": "Recipe IDs (alternative to meal_plan)" },
                  "meal_plan": { "type": "object", "description": "Meal plan structure (alternative to recipe_ids)" },
                  "unit_system": { "type": "string", "enum": ["metric", "imperial"], "default": "metric" },
                  "preferences": {
                    "type": "object",
                    "properties": {
                      "diet": { "type": "string", "description": "Dietary preference" },
                      "exclude_allergens": { "type": "array", "items": { "type": "string" }, "description": "Allergens to avoid in suggestions" },
                      "budget": { "type": "string", "enum": ["low", "normal", "premium"], "description": "Budget level" }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": { "description": "Optimized shopping list with AI suggestions", "content": { "application/json": { "schema": { "type": "object" } } } },
          "400": { "$ref": "#/components/responses/BadRequest" }
        }
      }
    },
    "/api/tips": {
      "post": {
        "operationId": "getTips",
        "summary": "AI cooking & prep tips",
        "description": "Get AI-powered cooking and preparation tips for any ingredient or recipe name. Supports any language. Optionally enriched with database context (allergens, cuisine, nutrition).",
        "tags": ["AI Tips"],
        "x-request-cost": 8,
        "x-tier-required": "indie",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["name", "type"],
                "properties": {
                  "name": { "type": "string", "minLength": 1, "maxLength": 200, "description": "Ingredient or recipe name (any language)" },
                  "type": { "type": "string", "enum": ["ingredient", "recipe"], "description": "Whether the name is an ingredient or recipe" },
                  "tip_type": { "type": "string", "enum": ["cooking", "prepping", "both"], "default": "both", "description": "Type of tips to return" }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Structured cooking/prepping tips",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": { "type": "boolean" },
                    "data": {
                      "type": "object",
                      "properties": {
                        "name": { "type": "string" },
                        "name_english": { "type": "string" },
                        "type": { "type": "string" },
                        "tip_type": { "type": "string" },
                        "tips": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "category": { "type": "string" },
                              "title": { "type": "string" },
                              "tip": { "type": "string" }
                            }
                          }
                        },
                        "tip_count": { "type": "integer" },
                        "db_enriched": { "type": "boolean" },
                        "metadata": {
                          "type": "object",
                          "properties": {
                            "analysis_time_ms": { "type": "integer" },
                            "model": { "type": "string" }
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "422": { "description": "AI could not generate tips for this input" },
          "503": { "description": "AI service temporarily unavailable" }
        }
      }
    },
    "/api/convert": {
      "get": {
        "operationId": "convertUnits",
        "summary": "General unit conversion",
        "description": "Convert between cooking measurement units with optional ingredient-specific density adjustments.",
        "tags": ["Utilities"],
        "x-request-cost": 1,
        "x-tier-required": "hobby",
        "parameters": [
          { "name": "amount", "in": "query", "required": true, "description": "Amount to convert", "schema": { "type": "number" } },
          { "name": "from", "in": "query", "required": true, "description": "Source unit (g, cup, tbsp, tsp, oz, lb, ml, etc.)", "schema": { "type": "string" } },
          { "name": "to", "in": "query", "description": "Target unit", "schema": { "type": "string", "default": "g" } },
          { "name": "ingredient", "in": "query", "description": "Ingredient name for density-adjusted conversion", "schema": { "type": "string" } }
        ],
        "responses": {
          "200": {
            "description": "Conversion result",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "original": { "type": "object", "properties": { "amount": { "type": "number" }, "unit": { "type": "string" } } },
                    "converted": { "type": "object", "properties": { "amount": { "type": "number" }, "unit": { "type": "string" } } },
                    "grams": { "type": "number" },
                    "ingredient": { "type": "string", "nullable": true }
                  }
                }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" }
        }
      }
    },
    "/api/allergens": {
      "get": {
        "operationId": "listAllergens",
        "summary": "List all allergens",
        "description": "Get the EU 14 mandatory allergens plus extended allergens tracked by Foodashi.",
        "tags": ["Utilities"],
        "x-request-cost": 1,
        "x-tier-required": "hobby",
        "responses": {
          "200": {
            "description": "Allergen reference data",
            "content": { "application/json": { "schema": { "type": "object" } } }
          }
        }
      }
    },
    "/api/diets": {
      "get": {
        "operationId": "listDietaryTags",
        "summary": "List all dietary tags",
        "description": "Get all dietary tags supported by Foodashi, categorized by type (lifestyle, allergen-free, nutritional, regional).",
        "tags": ["Utilities"],
        "x-request-cost": 1,
        "x-tier-required": "hobby",
        "responses": {
          "200": {
            "description": "Dietary tag reference data",
            "content": { "application/json": { "schema": { "type": "object" } } }
          }
        }
      }
    },
    "/api/cuisines": {
      "get": {
        "operationId": "listCuisines",
        "summary": "List all world cuisines",
        "description": "Get all world cuisines supported by Foodashi with region groupings and ISO country codes.",
        "tags": ["Utilities"],
        "x-request-cost": 1,
        "x-tier-required": "hobby",
        "responses": {
          "200": {
            "description": "Cuisine reference data",
            "content": { "application/json": { "schema": { "type": "object" } } }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "ApiKeyAuth": {
        "type": "apiKey",
        "in": "header",
        "name": "X-Api-Key",
        "description": "API key obtained from the Foodashi dashboard. Required for all endpoints."
      }
    },
    "parameters": {
      "Page": {
        "name": "page",
        "in": "query",
        "description": "Page number (1-based)",
        "schema": { "type": "integer", "minimum": 1, "default": 1 }
      },
      "Limit": {
        "name": "limit",
        "in": "query",
        "description": "Results per page",
        "schema": { "type": "integer", "minimum": 1, "maximum": 100, "default": 20 }
      },
      "RecipeId": {
        "name": "id",
        "in": "path",
        "required": true,
        "description": "Recipe UUID",
        "schema": { "type": "string", "format": "uuid" }
      },
      "IngredientId": {
        "name": "id",
        "in": "path",
        "required": true,
        "description": "Canonical ingredient UUID",
        "schema": { "type": "string", "format": "uuid" }
      }
    },
    "responses": {
      "BadRequest": {
        "description": "Invalid request parameters",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/Error" }
          }
        }
      },
      "NotFound": {
        "description": "Resource not found",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/Error" }
          }
        }
      },
      "InternalError": {
        "description": "Internal server error",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/Error" }
          }
        }
      },
      "RecipeCardList": {
        "description": "Paginated list of recipe cards",
        "content": {
          "application/json": {
            "schema": {
              "type": "object",
              "properties": {
                "recipes": {
                  "type": "array",
                  "items": { "$ref": "#/components/schemas/RecipeCard" }
                },
                "filter": { "type": "object" },
                "pagination": { "$ref": "#/components/schemas/Pagination" }
              }
            }
          }
        }
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string",
            "description": "Human-readable error message"
          }
        },
        "required": ["error"]
      },
      "Pagination": {
        "type": "object",
        "properties": {
          "page": { "type": "integer", "description": "Current page number" },
          "limit": { "type": "integer", "description": "Results per page" },
          "total": { "type": "integer", "description": "Total matching results" },
          "total_pages": { "type": "integer", "description": "Total number of pages" }
        }
      },
      "Recipe": {
        "type": "object",
        "description": "Full recipe object with computed nutrition",
        "properties": {
          "id": { "type": "string", "format": "uuid" },
          "title": { "type": "string", "description": "Recipe title in original language" },
          "title_english": { "type": "string", "nullable": true, "description": "Recipe title translated to English" },
          "description": { "type": "string" },
          "image_url": { "type": "string", "format": "uri", "nullable": true },
          "prep_time_minutes": { "type": "integer" },
          "cook_time_minutes": { "type": "integer" },
          "rest_time_minutes": { "type": "integer" },
          "total_time_minutes": { "type": "integer" },
          "servings": { "type": "integer" },
          "cuisine": { "type": "string" },
          "cuisine_code": { "type": "string", "description": "ISO 3166-1 alpha-2 country code for cuisine" },
          "meal_types": { "type": "array", "items": { "type": "string", "enum": ["Breakfast", "Brunch", "Lunch", "Dinner", "Late Night", "Appetizer", "Side", "Main Course", "Dessert", "Snack", "Tea Time", "Aperitif", "Drink", "Street Food", "Condiment", "Component"] }, "description": "16-value meal_types enum — WHEN/HOW it's eaten (course, role at the table). Multi-value; a recipe carries all that apply (e.g. Caesar Salad → ['Appetizer', 'Lunch', 'Side'])." },
          "dish_types": { "type": "array", "items": { "type": "string", "enum": ["Soup", "Stew", "Curry", "Porridge", "Sandwich", "Wrap", "Burger", "Hot Dog", "Spring Roll", "Pasta", "Noodle Dish", "Stuffed Pasta", "Rice Dish", "Fried Rice", "Sushi", "Grain Dish", "Salad", "Slaw", "Ceviche", "Tartare", "Stir-Fry", "Grilled Dish", "Roast", "Braised Dish", "Steamed Dish", "Barbecue", "Skewer", "Fried Dish", "Fritter", "Cutlet", "Croquette", "Casserole", "Gratin", "Quiche", "Savory Pie", "Soufflé", "Dumpling", "Empanada", "Hand Pie", "Pizza", "Calzone", "Stuffed Flatbread", "Bread", "Flatbread", "Cornbread", "Pancake", "Crepe", "Waffle", "Omelet", "Scrambled Eggs", "Egg Dish", "Bean Dish", "Lentil Dish", "Tofu Dish", "Vegetable Dish", "Stuffed Vegetable", "Fondue", "Cheese Plate", "Sausage", "Charcuterie", "Cured Fish", "Jerky", "Steak", "Roast Meat", "Ribs", "Meatball", "Ground Meat Dish", "Fish Fillet", "Whole Fish", "Shellfish", "Cake", "Cheesecake", "Cookie", "Sweet Pie", "Pastry", "Pudding", "Custard", "Frozen", "Confection", "Jelly", "Assembled", "Crumble", "Yogurt Dish", "Sauce", "Dressing", "Marinade", "Dip", "Spread", "Spice Paste", "Pickle", "Jam", "Compote", "Smoothie", "Milkshake", "Juice", "Cocktail", "Mocktail", "Tea", "Coffee", "Hot Chocolate", "Fermented Beverage", "Cracker", "Granola", "Energy Bar", "Popcorn", "Mixed Plate", "Bowl"] }, "description": "107-value dish_types enum — WHAT it structurally IS (the form of the food). Multi-value; a recipe carries all that apply (e.g. Bibimbap → ['Rice Dish', 'Bowl'])." },
          "difficulty": { "type": "string", "enum": ["Easy", "Medium", "Hard", "Expert"] },
          "cost_level": { "type": "integer", "nullable": true, "description": "Cost level 1-5" },
          "protein_source": { "type": "array", "items": { "type": "string" } },
          "tags": { "type": "array", "items": { "type": "string" } },
          "cooking_methods": { "type": "array", "items": { "type": "string" } },
          "occasions": { "type": "array", "items": { "type": "string" } },
          "seasons": { "type": "array", "items": { "type": "string" } },
          "equipment": { "type": "array", "items": { "type": "string" } },
          "dietary_tags": { "type": "array", "items": { "type": "string" } },
          "allergens": { "type": "array", "items": { "type": "string" } },
          "allergen_declaration": {
            "type": "object",
            "nullable": true,
            "properties": {
              "compliance": { "type": "string", "nullable": true },
              "declaration": { "type": "string", "nullable": true }
            }
          },
          "nutrition": { "$ref": "#/components/schemas/RecipeNutrition" },
          "taste_profile": { "$ref": "#/components/schemas/TasteProfile" },
          "storage": { "type": "object" },
          "nutrimetric": { "$ref": "#/components/schemas/NutriMetric" },
          "beverage_pairing": { "type": "object" },
          "ingredients": {
            "type": "array",
            "items": { "$ref": "#/components/schemas/RecipeIngredient" }
          },
          "steps": {
            "type": "array",
            "items": { "$ref": "#/components/schemas/RecipeStep" }
          }
        }
      },
      "RecipeCard": {
        "type": "object",
        "description": "Lightweight recipe card for browsing (no nutrition computation)",
        "properties": {
          "id": { "type": "string", "format": "uuid" },
          "title": { "type": "string" },
          "title_english": { "type": "string", "nullable": true },
          "image_url": { "type": "string", "format": "uri", "nullable": true },
          "cuisine": { "type": "string" },
          "cuisine_code": { "type": "string" },
          "meal_types": { "type": "array", "items": { "type": "string", "enum": ["Breakfast", "Brunch", "Lunch", "Dinner", "Late Night", "Appetizer", "Side", "Main Course", "Dessert", "Snack", "Tea Time", "Aperitif", "Drink", "Street Food", "Condiment", "Component"] }, "description": "16-value meal_types enum — WHEN/HOW it's eaten." },
          "dish_types": { "type": "array", "items": { "type": "string" }, "description": "107-value dish_types enum — WHAT it structurally IS." },
          "total_time_minutes": { "type": "integer", "nullable": true },
          "difficulty": { "type": "string" },
          "dietary_tags": { "type": "array", "items": { "type": "string" } },
          "servings": { "type": "integer" },
          "tags": { "type": "array", "items": { "type": "string" } },
          "published_at": { "type": "string", "format": "date-time" }
        }
      },
      "RecipeIngredient": {
        "type": "object",
        "properties": {
          "text": { "type": "string", "description": "Full display text (e.g., '2 cups flour')" },
          "name": { "type": "string", "description": "Ingredient name" },
          "quantity": { "type": "string", "description": "Amount as string" },
          "unit": { "type": "string", "description": "Measurement unit" },
          "is_main": { "type": "boolean", "description": "Whether this is a main ingredient" },
          "preparation": { "type": "string", "nullable": true, "description": "Preparation instructions (e.g., 'diced', 'minced')" },
          "optional": { "type": "boolean" },
          "gram_weight": { "type": "number", "nullable": true, "description": "Weight in grams" }
        }
      },
      "RecipeStep": {
        "type": "object",
        "properties": {
          "step": { "type": "integer", "description": "Step number" },
          "text": { "type": "string", "description": "Step instructions" },
          "duration_minutes": { "type": "integer", "nullable": true },
          "temperature": {
            "type": "object",
            "nullable": true,
            "properties": {
              "value": { "type": "number" },
              "unit": { "type": "string" }
            }
          },
          "tip": { "type": "string", "nullable": true }
        }
      },
      "RecipeNutrition": {
        "type": "object",
        "description": "Per-serving nutrition with 8 core nutrients",
        "properties": {
          "calories": { "type": "number", "description": "Energy in kcal" },
          "protein_g": { "type": "number" },
          "fat_g": { "type": "number" },
          "saturated_fat_g": { "type": "number" },
          "carbohydrate_g": { "type": "number" },
          "fiber_g": { "type": "number" },
          "sugars_total_g": { "type": "number" },
          "sodium_mg": { "type": "number" },
          "per_100g": { "type": "object", "description": "Same nutrients per 100g of cooked recipe" }
        }
      },
      "RecipeNutritionResponse": {
        "type": "object",
        "properties": {
          "id": { "type": "string", "format": "uuid" },
          "title": { "type": "string" },
          "servings": { "type": "integer" },
          "nutrition_per_serving": { "$ref": "#/components/schemas/RecipeNutrition" },
          "nutrition_meta": {
            "type": "object",
            "properties": {
              "confidence": { "type": "number" },
              "coverage": { "type": "number" },
              "ingredients_total": { "type": "integer" },
              "ingredients_resolved": { "type": "integer" }
            }
          },
          "ingredients": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "name": { "type": "string" },
                "quantity": { "type": "string" },
                "unit": { "type": "string" },
                "gram_weight": { "type": "number", "nullable": true },
                "is_main": { "type": "boolean" }
              }
            }
          }
        }
      },
      "TasteProfile": {
        "type": "object",
        "description": "6-axis taste profile on a 0-10 scale",
        "properties": {
          "spicy": { "type": "integer", "minimum": 0, "maximum": 10 },
          "salty": { "type": "integer", "minimum": 0, "maximum": 10 },
          "sweet": { "type": "integer", "minimum": 0, "maximum": 10 },
          "umami": { "type": "integer", "minimum": 0, "maximum": 10 },
          "bitter": { "type": "integer", "minimum": 0, "maximum": 10 },
          "sour": { "type": "integer", "minimum": 0, "maximum": 10 },
          "scale": { "type": "string", "description": "Scale description", "example": "0-10" }
        }
      },
      "NutriMetric": {
        "type": "object",
        "description": "Foodashi proprietary nutrition quality score",
        "nullable": true,
        "properties": {
          "grade": { "type": "string", "enum": ["A", "B", "C", "D", "E", "F"], "description": "Letter grade A (excellent) to F (very poor)" },
          "score": { "type": "integer", "minimum": 0, "maximum": 100, "description": "Numeric score 0-100" },
          "label": { "type": "string", "description": "Human-readable label (Excellent, Good, Moderate, Poor, Very Poor)", "nullable": true },
          "version": { "type": "string", "description": "Scoring algorithm version" },
          "category": { "type": "string", "description": "Recipe category used for scoring" },
          "breakdown": { "type": "object", "description": "Detailed scoring breakdown", "nullable": true }
        }
      },
      "ScaledIngredient": {
        "type": "object",
        "properties": {
          "name": { "type": "string" },
          "original_quantity": { "type": "number" },
          "scaled_quantity": { "type": "number" },
          "unit": { "type": "string" },
          "original_gram_weight": { "type": "number", "nullable": true },
          "scaled_gram_weight": { "type": "number", "nullable": true },
          "is_main": { "type": "boolean" },
          "preparation": { "type": "string", "nullable": true },
          "optional": { "type": "boolean" }
        }
      },
      "IngredientSearchResult": {
        "type": "object",
        "properties": {
          "id": { "type": "string", "format": "uuid" },
          "name": { "type": "string", "description": "Display name (cleaned)" },
          "original_name": { "type": "string", "description": "Original USDA/EU database name" },
          "slug": { "type": "string" },
          "type": { "type": "string", "enum": ["raw", "branded", "cooked", "processed"] },
          "ingredient_type": { "type": "string", "enum": ["raw", "branded", "cooked", "processed"] },
          "food_group": { "type": "string", "nullable": true },
          "has_nutrition": { "type": "boolean" },
          "nutrition_per_100g": {
            "type": "object",
            "description": "Core nutrition per 100g (null values indicate missing data)",
            "properties": {
              "calories": { "type": "number", "nullable": true },
              "protein": { "type": "number", "nullable": true },
              "carbohydrates": { "type": "number", "nullable": true },
              "fat": { "type": "number", "nullable": true },
              "fiber": { "type": "number", "nullable": true },
              "sugar": { "type": "number", "nullable": true },
              "sodium": { "type": "number", "nullable": true }
            }
          },
          "available_portions": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "unit": { "type": "string" },
                "grams": { "type": "number" }
              }
            }
          }
        }
      },
      "IngredientDetail": {
        "type": "object",
        "properties": {
          "id": { "type": "string", "format": "uuid" },
          "name": { "type": "string" },
          "original_name": { "type": "string" },
          "slug": { "type": "string" },
          "type": { "type": "string" },
          "ingredient_type": { "type": "string" },
          "food_group": { "type": "string", "nullable": true },
          "category": { "type": "string", "nullable": true },
          "is_branded": { "type": "boolean" },
          "has_nutrition": { "type": "boolean" },
          "is_complete": { "type": "boolean", "description": "Whether full NutriMetric data is available" },
          "recipe_usable": { "type": "boolean", "description": "Whether this ingredient is in the golden pool for recipe generation" },
          "nutrition_per_100g": {
            "type": "object",
            "properties": {
              "calories": { "type": "number", "nullable": true },
              "protein": { "type": "number", "nullable": true },
              "carbohydrates": { "type": "number", "nullable": true },
              "fat": { "type": "number", "nullable": true },
              "fiber": { "type": "number", "nullable": true },
              "sugar": { "type": "number", "nullable": true },
              "sodium": { "type": "number", "nullable": true }
            }
          },
          "nutrition_detailed": {
            "type": "object",
            "description": "Full nutrition grouped by category (Macros, Vitamins, Minerals, etc.)",
            "additionalProperties": {
              "type": "array",
              "items": {
                "type": "object",
                "properties": {
                  "key": { "type": "string" },
                  "name": { "type": "string" },
                  "value": { "type": "number", "nullable": true },
                  "unit": { "type": "string" },
                  "sources_count": { "type": "integer" },
                  "confidence": { "type": "number" }
                }
              }
            }
          },
          "portions": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "unit_name": { "type": "string" },
                "gram_weight": { "type": "number" }
              }
            }
          }
        }
      },
      "NutrientDef": {
        "type": "object",
        "properties": {
          "id": { "type": "integer" },
          "key": { "type": "string", "description": "Machine-readable key (e.g., energy_kcal, protein)" },
          "name": { "type": "string", "description": "Human-readable name" },
          "unit": { "type": "string", "description": "Measurement unit (g, mg, mcg, kcal)" },
          "group_name": { "type": "string", "description": "Category (Macros, Vitamins, Minerals, etc.)" }
        }
      },
      "ShoppingListResponse": {
        "type": "object",
        "properties": {
          "success": { "type": "boolean" },
          "shopping_list": {
            "type": "object",
            "properties": {
              "sections": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "name": { "type": "string", "description": "Store section (Produce, Protein, Dairy & Eggs, Grains & Bread, Pantry, Beverages, Other)" },
                    "items": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "ingredient": { "type": "string" },
                          "canonical_id": { "type": "string", "format": "uuid" },
                          "quantity": { "type": "string" },
                          "grams": { "type": "number" },
                          "recipes_used_in": { "type": "array", "items": { "type": "string", "format": "uuid" } },
                          "is_main": { "type": "boolean" },
                          "optional": { "type": "boolean" }
                        }
                      }
                    }
                  }
                }
              },
              "total_items": { "type": "integer" },
              "recipes_found": { "type": "integer" },
              "recipes_requested": { "type": "integer" }
            }
          }
        }
      }
    }
  }
}