{
  "openapi": "3.1.0",
  "info": {
    "title": "NutraPlanner public API",
    "version": "1.0.0",
    "summary": "Read-only nutrition calculators: BMI, BMR, TDEE, calorie and macro targets, protein, body fat, water, ideal weight.",
    "description": "Stateless, read-only nutrition calculators — the same formulas as the free calculators at https://nutraplanner.com/resources/tools.\n\nNo authentication or API key is required. The application does not store request inputs. Inputs are metric (kg, cm).\n\nRate limit: 120 requests per minute per client IP (an IPv6 client counts per /64). Errors use RFC 9457 problem details.\n\nResults are general estimates for healthy adults and are not medical advice.\n\nThis API does not expose practitioner or client data; there is no API for NutraPlanner accounts.",
    "termsOfService": "https://nutraplanner.com/policies/tos",
    "contact": {
      "name": "NutraPlanner",
      "email": "hello@nutraplanner.com",
      "url": "https://nutraplanner.com/developers"
    }
  },
  "externalDocs": {
    "description": "Developer documentation",
    "url": "https://nutraplanner.com/developers"
  },
  "servers": [
    {
      "url": "https://nutraplanner.com",
      "description": "Production"
    }
  ],
  "security": [],
  "tags": [
    {
      "name": "calculators",
      "description": "Nutrition and body-composition calculators."
    }
  ],
  "paths": {
    "/api/public/v1/calculators/bmi": {
      "get": {
        "operationId": "calculateBmi",
        "summary": "Body Mass Index",
        "description": "Body Mass Index (kg/m²) with the WHO adult category. The same formula powers the free web calculator at https://nutraplanner.com/resources/tools/bmi. Estimates for healthy adults, not medical advice.",
        "tags": [
          "calculators"
        ],
        "security": [],
        "parameters": [
          {
            "name": "weight_kg",
            "in": "query",
            "required": true,
            "description": "Body weight in kilograms.",
            "schema": {
              "type": "number",
              "minimum": 20,
              "maximum": 500
            },
            "example": 68
          },
          {
            "name": "height_cm",
            "in": "query",
            "required": true,
            "description": "Height in centimetres.",
            "schema": {
              "type": "number",
              "minimum": 100,
              "maximum": 250
            },
            "example": 165
          }
        ],
        "responses": {
          "200": {
            "description": "The calculated result.",
            "headers": {
              "Cache-Control": {
                "description": "Results are deterministic: the caller may cache them (private — a shared cache must not store query inputs).",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BmiResult"
                }
              }
            }
          },
          "400": {
            "description": "A parameter is missing, unknown, repeated, not a plain decimal number, or out of range.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "405": {
            "description": "The method is not GET, HEAD or OPTIONS; the API is read-only.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "headers": {
              "Allow": {
                "description": "The methods this resource accepts.",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "422": {
            "description": "The parameters are valid individually but cannot be computed together.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (120 requests per minute per client).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds until the limit resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "500": {
            "description": "An unexpected server error; the request can be retried.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/api/public/v1/calculators/bmr": {
      "get": {
        "operationId": "calculateBmr",
        "summary": "Basal metabolic rate",
        "description": "Basal metabolic rate (kcal/day) by Mifflin-St Jeor (default), revised Harris-Benedict, or Katch-McArdle. The same formula powers the free web calculator at https://nutraplanner.com/resources/tools/tdee. Estimates for healthy adults, not medical advice.",
        "tags": [
          "calculators"
        ],
        "security": [],
        "parameters": [
          {
            "name": "sex",
            "in": "query",
            "required": true,
            "description": "Sex used by the equation.",
            "schema": {
              "type": "string",
              "enum": [
                "female",
                "male"
              ]
            },
            "example": "female"
          },
          {
            "name": "age",
            "in": "query",
            "required": true,
            "description": "Age in years (adults).",
            "schema": {
              "type": "number",
              "minimum": 18,
              "maximum": 120
            },
            "example": 35
          },
          {
            "name": "weight_kg",
            "in": "query",
            "required": true,
            "description": "Body weight in kilograms.",
            "schema": {
              "type": "number",
              "minimum": 20,
              "maximum": 500
            },
            "example": 68
          },
          {
            "name": "height_cm",
            "in": "query",
            "required": true,
            "description": "Height in centimetres.",
            "schema": {
              "type": "number",
              "minimum": 100,
              "maximum": 250
            },
            "example": 165
          },
          {
            "name": "formula",
            "in": "query",
            "required": false,
            "description": "BMR equation. `katch_mcardle` requires `body_fat_pct`.",
            "schema": {
              "type": "string",
              "enum": [
                "mifflin_st_jeor",
                "harris_benedict",
                "katch_mcardle"
              ],
              "default": "mifflin_st_jeor"
            },
            "example": "mifflin_st_jeor"
          },
          {
            "name": "body_fat_pct",
            "in": "query",
            "required": false,
            "description": "Body-fat percentage. Required only for `formula=katch_mcardle`.",
            "schema": {
              "type": "number",
              "minimum": 2,
              "maximum": 70
            },
            "example": 28
          }
        ],
        "responses": {
          "200": {
            "description": "The calculated result.",
            "headers": {
              "Cache-Control": {
                "description": "Results are deterministic: the caller may cache them (private — a shared cache must not store query inputs).",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BmrResult"
                }
              }
            }
          },
          "400": {
            "description": "A parameter is missing, unknown, repeated, not a plain decimal number, or out of range.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "405": {
            "description": "The method is not GET, HEAD or OPTIONS; the API is read-only.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "headers": {
              "Allow": {
                "description": "The methods this resource accepts.",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "422": {
            "description": "The parameters are valid individually but cannot be computed together.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (120 requests per minute per client).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds until the limit resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "500": {
            "description": "An unexpected server error; the request can be retried.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/api/public/v1/calculators/tdee": {
      "get": {
        "operationId": "calculateTdee",
        "summary": "Total daily energy expenditure",
        "description": "Total daily energy expenditure (kcal/day): BMR multiplied by an activity factor. The same formula powers the free web calculator at https://nutraplanner.com/resources/tools/tdee. Estimates for healthy adults, not medical advice.",
        "tags": [
          "calculators"
        ],
        "security": [],
        "parameters": [
          {
            "name": "sex",
            "in": "query",
            "required": true,
            "description": "Sex used by the equation.",
            "schema": {
              "type": "string",
              "enum": [
                "female",
                "male"
              ]
            },
            "example": "female"
          },
          {
            "name": "age",
            "in": "query",
            "required": true,
            "description": "Age in years (adults).",
            "schema": {
              "type": "number",
              "minimum": 18,
              "maximum": 120
            },
            "example": 35
          },
          {
            "name": "weight_kg",
            "in": "query",
            "required": true,
            "description": "Body weight in kilograms.",
            "schema": {
              "type": "number",
              "minimum": 20,
              "maximum": 500
            },
            "example": 68
          },
          {
            "name": "height_cm",
            "in": "query",
            "required": true,
            "description": "Height in centimetres.",
            "schema": {
              "type": "number",
              "minimum": 100,
              "maximum": 250
            },
            "example": 165
          },
          {
            "name": "activity",
            "in": "query",
            "required": true,
            "description": "Activity level; multiplier applied to BMR (sedentary ×1.2, light ×1.375, moderate ×1.55, active ×1.725, very_active ×1.9).",
            "schema": {
              "type": "string",
              "enum": [
                "sedentary",
                "light",
                "moderate",
                "active",
                "very_active"
              ]
            },
            "example": "moderate"
          },
          {
            "name": "formula",
            "in": "query",
            "required": false,
            "description": "BMR equation. `katch_mcardle` requires `body_fat_pct`.",
            "schema": {
              "type": "string",
              "enum": [
                "mifflin_st_jeor",
                "harris_benedict",
                "katch_mcardle"
              ],
              "default": "mifflin_st_jeor"
            },
            "example": "mifflin_st_jeor"
          },
          {
            "name": "body_fat_pct",
            "in": "query",
            "required": false,
            "description": "Body-fat percentage. Required only for `formula=katch_mcardle`.",
            "schema": {
              "type": "number",
              "minimum": 2,
              "maximum": 70
            },
            "example": 28
          }
        ],
        "responses": {
          "200": {
            "description": "The calculated result.",
            "headers": {
              "Cache-Control": {
                "description": "Results are deterministic: the caller may cache them (private — a shared cache must not store query inputs).",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TdeeResult"
                }
              }
            }
          },
          "400": {
            "description": "A parameter is missing, unknown, repeated, not a plain decimal number, or out of range.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "405": {
            "description": "The method is not GET, HEAD or OPTIONS; the API is read-only.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "headers": {
              "Allow": {
                "description": "The methods this resource accepts.",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "422": {
            "description": "The parameters are valid individually but cannot be computed together.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (120 requests per minute per client).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds until the limit resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "500": {
            "description": "An unexpected server error; the request can be retried.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/api/public/v1/calculators/calorie-target": {
      "get": {
        "operationId": "calculateCalorieTarget",
        "summary": "Daily calorie target",
        "description": "Daily calorie target (kcal/day): TDEE adjusted for a weight goal (about ±250 or ±500 kcal/day). The same formula powers the free web calculator at https://nutraplanner.com/resources/tools/calorie. Estimates for healthy adults, not medical advice.",
        "tags": [
          "calculators"
        ],
        "security": [],
        "parameters": [
          {
            "name": "sex",
            "in": "query",
            "required": true,
            "description": "Sex used by the equation.",
            "schema": {
              "type": "string",
              "enum": [
                "female",
                "male"
              ]
            },
            "example": "female"
          },
          {
            "name": "age",
            "in": "query",
            "required": true,
            "description": "Age in years (adults).",
            "schema": {
              "type": "number",
              "minimum": 18,
              "maximum": 120
            },
            "example": 35
          },
          {
            "name": "weight_kg",
            "in": "query",
            "required": true,
            "description": "Body weight in kilograms.",
            "schema": {
              "type": "number",
              "minimum": 20,
              "maximum": 500
            },
            "example": 68
          },
          {
            "name": "height_cm",
            "in": "query",
            "required": true,
            "description": "Height in centimetres.",
            "schema": {
              "type": "number",
              "minimum": 100,
              "maximum": 250
            },
            "example": 165
          },
          {
            "name": "activity",
            "in": "query",
            "required": true,
            "description": "Activity level; multiplier applied to BMR (sedentary ×1.2, light ×1.375, moderate ×1.55, active ×1.725, very_active ×1.9).",
            "schema": {
              "type": "string",
              "enum": [
                "sedentary",
                "light",
                "moderate",
                "active",
                "very_active"
              ]
            },
            "example": "moderate"
          },
          {
            "name": "goal",
            "in": "query",
            "required": true,
            "description": "Weight goal (lose -500, mild_lose -250, maintain +0, mild_gain +250, gain +500 kcal/day).",
            "schema": {
              "type": "string",
              "enum": [
                "lose",
                "mild_lose",
                "maintain",
                "mild_gain",
                "gain"
              ]
            },
            "example": "mild_lose"
          },
          {
            "name": "formula",
            "in": "query",
            "required": false,
            "description": "BMR equation.",
            "schema": {
              "type": "string",
              "enum": [
                "mifflin_st_jeor",
                "harris_benedict"
              ],
              "default": "mifflin_st_jeor"
            },
            "example": "mifflin_st_jeor"
          }
        ],
        "responses": {
          "200": {
            "description": "The calculated result.",
            "headers": {
              "Cache-Control": {
                "description": "Results are deterministic: the caller may cache them (private — a shared cache must not store query inputs).",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CalorieTargetResult"
                }
              }
            }
          },
          "400": {
            "description": "A parameter is missing, unknown, repeated, not a plain decimal number, or out of range.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "405": {
            "description": "The method is not GET, HEAD or OPTIONS; the API is read-only.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "headers": {
              "Allow": {
                "description": "The methods this resource accepts.",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "422": {
            "description": "The parameters are valid individually but cannot be computed together.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (120 requests per minute per client).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds until the limit resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "500": {
            "description": "An unexpected server error; the request can be retried.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/api/public/v1/calculators/macros": {
      "get": {
        "operationId": "calculateMacros",
        "summary": "Macronutrient grams from a calorie target",
        "description": "Grams of protein, carbohydrate and fat for a calorie target and a split preset (4/4/9 kcal per gram). Every preset keeps protein within the 10–35% AMDR. The same formula powers the free web calculator at https://nutraplanner.com/resources/tools/macro. Estimates for healthy adults, not medical advice.",
        "tags": [
          "calculators"
        ],
        "security": [],
        "parameters": [
          {
            "name": "calories",
            "in": "query",
            "required": true,
            "description": "Daily calorie target, kcal.",
            "schema": {
              "type": "number",
              "minimum": 500,
              "maximum": 10000
            },
            "example": 2000
          },
          {
            "name": "split",
            "in": "query",
            "required": false,
            "description": "Split preset as protein/carbs/fat shares (balanced 25/45/30, low_carb 25/35/40, high_protein 30/45/25, high_carb 20/60/20).",
            "schema": {
              "type": "string",
              "enum": [
                "balanced",
                "low_carb",
                "high_protein",
                "high_carb"
              ],
              "default": "balanced"
            },
            "example": "balanced"
          }
        ],
        "responses": {
          "200": {
            "description": "The calculated result.",
            "headers": {
              "Cache-Control": {
                "description": "Results are deterministic: the caller may cache them (private — a shared cache must not store query inputs).",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MacrosResult"
                }
              }
            }
          },
          "400": {
            "description": "A parameter is missing, unknown, repeated, not a plain decimal number, or out of range.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "405": {
            "description": "The method is not GET, HEAD or OPTIONS; the API is read-only.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "headers": {
              "Allow": {
                "description": "The methods this resource accepts.",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "422": {
            "description": "The parameters are valid individually but cannot be computed together.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (120 requests per minute per client).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds until the limit resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "500": {
            "description": "An unexpected server error; the request can be retried.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/api/public/v1/calculators/protein": {
      "get": {
        "operationId": "calculateProteinTarget",
        "summary": "Daily protein target",
        "description": "Daily protein target (grams) from body weight and an activity goal, in g/kg. The same formula powers the free web calculator at https://nutraplanner.com/resources/tools/protein. Estimates for healthy adults, not medical advice.",
        "tags": [
          "calculators"
        ],
        "security": [],
        "parameters": [
          {
            "name": "weight_kg",
            "in": "query",
            "required": true,
            "description": "Body weight in kilograms.",
            "schema": {
              "type": "number",
              "minimum": 20,
              "maximum": 500
            },
            "example": 68
          },
          {
            "name": "goal",
            "in": "query",
            "required": true,
            "description": "Goal (sedentary 1 g/kg, active 1.2 g/kg, build_muscle 1.6 g/kg, athlete 2 g/kg).",
            "schema": {
              "type": "string",
              "enum": [
                "sedentary",
                "active",
                "build_muscle",
                "athlete"
              ]
            },
            "example": "active"
          }
        ],
        "responses": {
          "200": {
            "description": "The calculated result.",
            "headers": {
              "Cache-Control": {
                "description": "Results are deterministic: the caller may cache them (private — a shared cache must not store query inputs).",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProteinTargetResult"
                }
              }
            }
          },
          "400": {
            "description": "A parameter is missing, unknown, repeated, not a plain decimal number, or out of range.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "405": {
            "description": "The method is not GET, HEAD or OPTIONS; the API is read-only.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "headers": {
              "Allow": {
                "description": "The methods this resource accepts.",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "422": {
            "description": "The parameters are valid individually but cannot be computed together.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (120 requests per minute per client).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds until the limit resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "500": {
            "description": "An unexpected server error; the request can be retried.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/api/public/v1/calculators/body-fat": {
      "get": {
        "operationId": "calculateBodyFatNavy",
        "summary": "Body-fat percentage (U.S. Navy method)",
        "description": "Body-fat percentage from circumference measurements by the U.S. Navy method. `hip_cm` is required for `sex=female`. The same formula powers the free web calculator at https://nutraplanner.com/resources/tools/body-fat. Estimates for healthy adults, not medical advice.",
        "tags": [
          "calculators"
        ],
        "security": [],
        "parameters": [
          {
            "name": "sex",
            "in": "query",
            "required": true,
            "description": "Sex used by the equation.",
            "schema": {
              "type": "string",
              "enum": [
                "female",
                "male"
              ]
            },
            "example": "female"
          },
          {
            "name": "height_cm",
            "in": "query",
            "required": true,
            "description": "Height in centimetres.",
            "schema": {
              "type": "number",
              "minimum": 100,
              "maximum": 250
            },
            "example": 165
          },
          {
            "name": "neck_cm",
            "in": "query",
            "required": true,
            "description": "Neck circumference, cm.",
            "schema": {
              "type": "number",
              "minimum": 20,
              "maximum": 100
            },
            "example": 33
          },
          {
            "name": "waist_cm",
            "in": "query",
            "required": true,
            "description": "Waist circumference, cm.",
            "schema": {
              "type": "number",
              "minimum": 40,
              "maximum": 250
            },
            "example": 76
          },
          {
            "name": "hip_cm",
            "in": "query",
            "required": false,
            "description": "Hip circumference, cm. Required for `sex=female`.",
            "schema": {
              "type": "number",
              "minimum": 40,
              "maximum": 250
            },
            "example": 98
          }
        ],
        "responses": {
          "200": {
            "description": "The calculated result.",
            "headers": {
              "Cache-Control": {
                "description": "Results are deterministic: the caller may cache them (private — a shared cache must not store query inputs).",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BodyFatNavyResult"
                }
              }
            }
          },
          "400": {
            "description": "A parameter is missing, unknown, repeated, not a plain decimal number, or out of range.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "405": {
            "description": "The method is not GET, HEAD or OPTIONS; the API is read-only.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "headers": {
              "Allow": {
                "description": "The methods this resource accepts.",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "422": {
            "description": "The parameters are valid individually but cannot be computed together.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (120 requests per minute per client).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds until the limit resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "500": {
            "description": "An unexpected server error; the request can be retried.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/api/public/v1/calculators/water": {
      "get": {
        "operationId": "calculateWaterIntake",
        "summary": "Daily water intake",
        "description": "Estimated daily fluid intake (mL) from body weight and activity, in mL per kg. The same formula powers the free web calculator at https://nutraplanner.com/resources/tools/water. Estimates for healthy adults, not medical advice.",
        "tags": [
          "calculators"
        ],
        "security": [],
        "parameters": [
          {
            "name": "weight_kg",
            "in": "query",
            "required": true,
            "description": "Body weight in kilograms.",
            "schema": {
              "type": "number",
              "minimum": 20,
              "maximum": 500
            },
            "example": 68
          },
          {
            "name": "activity",
            "in": "query",
            "required": true,
            "description": "Activity (sedentary 30 mL/kg, active 35 mL/kg, athlete 40 mL/kg).",
            "schema": {
              "type": "string",
              "enum": [
                "sedentary",
                "active",
                "athlete"
              ]
            },
            "example": "active"
          }
        ],
        "responses": {
          "200": {
            "description": "The calculated result.",
            "headers": {
              "Cache-Control": {
                "description": "Results are deterministic: the caller may cache them (private — a shared cache must not store query inputs).",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WaterIntakeResult"
                }
              }
            }
          },
          "400": {
            "description": "A parameter is missing, unknown, repeated, not a plain decimal number, or out of range.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "405": {
            "description": "The method is not GET, HEAD or OPTIONS; the API is read-only.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "headers": {
              "Allow": {
                "description": "The methods this resource accepts.",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "422": {
            "description": "The parameters are valid individually but cannot be computed together.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (120 requests per minute per client).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds until the limit resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "500": {
            "description": "An unexpected server error; the request can be retried.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/api/public/v1/calculators/ideal-weight": {
      "get": {
        "operationId": "calculateIdealBodyWeight",
        "summary": "Ideal and adjusted body weight (Devine)",
        "description": "Ideal body weight (kg) by the Devine formula. With `weight_kg`, also the adjusted body weight used clinically when actual weight exceeds ideal. The same formula powers the free web calculator at https://nutraplanner.com/resources/tools/ideal-weight. Estimates for healthy adults, not medical advice.",
        "tags": [
          "calculators"
        ],
        "security": [],
        "parameters": [
          {
            "name": "sex",
            "in": "query",
            "required": true,
            "description": "Sex used by the equation.",
            "schema": {
              "type": "string",
              "enum": [
                "female",
                "male"
              ]
            },
            "example": "female"
          },
          {
            "name": "height_cm",
            "in": "query",
            "required": true,
            "description": "Height in centimetres.",
            "schema": {
              "type": "number",
              "minimum": 100,
              "maximum": 250
            },
            "example": 165
          },
          {
            "name": "weight_kg",
            "in": "query",
            "required": false,
            "description": "Actual body weight in kilograms, for adjusted body weight.",
            "schema": {
              "type": "number",
              "minimum": 20,
              "maximum": 500
            },
            "example": 68
          }
        ],
        "responses": {
          "200": {
            "description": "The calculated result.",
            "headers": {
              "Cache-Control": {
                "description": "Results are deterministic: the caller may cache them (private — a shared cache must not store query inputs).",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IdealBodyWeightResult"
                }
              }
            }
          },
          "400": {
            "description": "A parameter is missing, unknown, repeated, not a plain decimal number, or out of range.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "405": {
            "description": "The method is not GET, HEAD or OPTIONS; the API is read-only.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "headers": {
              "Allow": {
                "description": "The methods this resource accepts.",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "422": {
            "description": "The parameters are valid individually but cannot be computed together.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (120 requests per minute per client).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds until the limit resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "500": {
            "description": "An unexpected server error; the request can be retried.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "Problem": {
        "type": "object",
        "description": "RFC 9457 problem details.",
        "required": [
          "type",
          "title",
          "status"
        ],
        "properties": {
          "type": {
            "type": "string",
            "format": "uri",
            "description": "URI identifying the problem kind."
          },
          "title": {
            "type": "string",
            "description": "Short summary of the problem kind."
          },
          "status": {
            "type": "integer",
            "description": "HTTP status code."
          },
          "detail": {
            "type": "string",
            "description": "Explanation specific to this request."
          },
          "errors": {
            "type": "array",
            "description": "Per-parameter validation errors (invalid-parameters only).",
            "items": {
              "type": "object",
              "required": [
                "parameter",
                "message"
              ],
              "properties": {
                "parameter": {
                  "type": "string"
                },
                "message": {
                  "type": "string"
                }
              }
            }
          }
        }
      },
      "BmiResult": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "bmi",
          "category"
        ],
        "properties": {
          "bmi": {
            "type": "number",
            "description": "Body Mass Index, kg/m², two decimals. The category is taken from this rounded value, so the two never disagree at a cut-off."
          },
          "category": {
            "type": "string",
            "enum": [
              "underweight",
              "normal",
              "overweight",
              "obese"
            ],
            "description": "WHO adult category."
          }
        }
      },
      "BmrResult": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "bmr_kcal",
          "formula"
        ],
        "properties": {
          "bmr_kcal": {
            "type": "integer",
            "description": "Basal metabolic rate, kcal/day."
          },
          "formula": {
            "type": "string",
            "enum": [
              "mifflin_st_jeor",
              "harris_benedict",
              "katch_mcardle"
            ],
            "description": "Equation used."
          }
        }
      },
      "TdeeResult": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "bmr_kcal",
          "tdee_kcal",
          "activity_multiplier",
          "formula"
        ],
        "properties": {
          "bmr_kcal": {
            "type": "integer",
            "description": "Basal metabolic rate, kcal/day."
          },
          "tdee_kcal": {
            "type": "integer",
            "description": "Total daily energy expenditure, kcal/day."
          },
          "activity_multiplier": {
            "type": "number",
            "description": "Multiplier applied to BMR."
          },
          "formula": {
            "type": "string",
            "enum": [
              "mifflin_st_jeor",
              "harris_benedict",
              "katch_mcardle"
            ],
            "description": "BMR equation used."
          }
        }
      },
      "CalorieTargetResult": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "tdee_kcal",
          "adjustment_kcal",
          "target_kcal"
        ],
        "properties": {
          "tdee_kcal": {
            "type": "integer",
            "description": "Total daily energy expenditure, kcal/day."
          },
          "adjustment_kcal": {
            "type": "integer",
            "description": "Goal adjustment applied to TDEE, kcal/day."
          },
          "target_kcal": {
            "type": "integer",
            "description": "Daily calorie target, kcal/day."
          }
        }
      },
      "MacrosResult": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "protein_g",
          "carbs_g",
          "fat_g",
          "split"
        ],
        "properties": {
          "protein_g": {
            "type": "integer",
            "description": "Protein, grams/day."
          },
          "carbs_g": {
            "type": "integer",
            "description": "Carbohydrate, grams/day."
          },
          "fat_g": {
            "type": "integer",
            "description": "Fat, grams/day."
          },
          "split": {
            "type": "string",
            "enum": [
              "balanced",
              "low_carb",
              "high_protein",
              "high_carb"
            ],
            "description": "Split preset used."
          }
        }
      },
      "ProteinTargetResult": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "protein_g",
          "g_per_kg"
        ],
        "properties": {
          "protein_g": {
            "type": "integer",
            "description": "Protein, grams/day."
          },
          "g_per_kg": {
            "type": "number",
            "description": "Grams of protein per kilogram of body weight."
          }
        }
      },
      "BodyFatNavyResult": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "body_fat_pct"
        ],
        "properties": {
          "body_fat_pct": {
            "type": "number",
            "description": "Estimated body fat, percent, one decimal (always 2–70)."
          }
        }
      },
      "WaterIntakeResult": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "water_ml",
          "ml_per_kg"
        ],
        "properties": {
          "water_ml": {
            "type": "integer",
            "description": "Daily fluid, millilitres."
          },
          "ml_per_kg": {
            "type": "number",
            "description": "Millilitres per kilogram of body weight."
          }
        }
      },
      "IdealBodyWeightResult": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "ideal_weight_kg",
          "adjusted_weight_kg"
        ],
        "properties": {
          "ideal_weight_kg": {
            "type": "number",
            "description": "Ideal body weight, kg, one decimal."
          },
          "adjusted_weight_kg": {
            "type": [
              "number",
              "null"
            ],
            "description": "Adjusted body weight, kg; null when weight_kg is not given."
          }
        }
      }
    }
  }
}