{
  "openapi": "3.0.3",
  "info": {
    "title": "Chakudya Nutrition Registry API (CNR)",
    "description": "Chakudya is a Malawi focused nutrition data and intelligence API. It provides structured, searchable nutrition information for health applications, researchers, students, dietitians, and developers building tools for clinical or public health nutrition work. The API combines local food composition data with international sources such as USDA FoodData Central, Open Food Facts, and FatSecret, and adds retrieval augmented question answering and session memory for AI assisted applications.\n\nIt also covers clinical growth assessment (WHO child growth standards from birth to 19 years, Fenton preterm charts, and a combined `/growth/assess` call), recipe and meal nutrition analysis, dietary reference intakes, and a stateless FHIR R4 facade that exposes growth evaluation and the full Nutrition Care Process (screening, assessment, diagnosis, intervention, monitoring and ADIME documents).\n\nPeople can sign in on the docs site and create their own consumer API keys (higher rate limits, FHIR access, usage counts) with `/developer/keys`.\n\nEvery operational route needs `Authorization: Bearer YOUR_API_KEY` (a consumer key from the developer console, or an admin or reviewer key). The only routes that work without a key are `GET /`, `GET /health` and the FHIR conformance reads (`GET /fhir/metadata`, `CodeSystem`, `OperationDefinition`, `StructureDefinition`, `ConceptMap`, `Questionnaire`). `/developer/keys` takes your Supabase login token instead of an API key, and `POST /rag/webhook` and `POST /hooks/signup` take a shared secret. Privileged registry writes (foods, exchange, renal, formulas, drug interactions, glycaemic index, reference tables, packaged CRUD and review, RAG ingest, bulk inserts) and key management need an admin key; a consumer key is refused there. Anonymous calls receive 401 with the code `api_key_required`. OAuth 2.0 was removed in v1.34.0.\n",
    "version": "1.74.0",
    "contact": {
      "name": "Edison Taimu",
      "x-location": "Blantyre, Malawi"
    },
    "license": {
      "name": "UNLICENSED"
    }
  },
  "servers": [
    {
      "url": "https://api.chakudya.xyz",
      "description": "Production (Cloudflare Workers)"
    }
  ],
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "tags": [
    {
      "name": "Meta"
    },
    {
      "name": "Health"
    },
    {
      "name": "Batch"
    },
    {
      "name": "API Keys"
    },
    {
      "name": "Developer keys"
    },
    {
      "name": "Foods"
    },
    {
      "name": "Glycaemic Index"
    },
    {
      "name": "Exchange"
    },
    {
      "name": "Renal"
    },
    {
      "name": "Formulas"
    },
    {
      "name": "Drug Interactions"
    },
    {
      "name": "BMI for age"
    },
    {
      "name": "HC velocity"
    },
    {
      "name": "Length velocity"
    },
    {
      "name": "Weight velocity"
    },
    {
      "name": "MUAC for age"
    },
    {
      "name": "MUAC fixed cut-offs"
    },
    {
      "name": "Length height for age"
    },
    {
      "name": "Weight for age"
    },
    {
      "name": "Weight for length height"
    },
    {
      "name": "BMI for age under 5"
    },
    {
      "name": "HC for age"
    },
    {
      "name": "Growth assessment"
    },
    {
      "name": "Energy"
    },
    {
      "name": "Sports Nutrition"
    },
    {
      "name": "Weight Management"
    },
    {
      "name": "Pregnancy"
    },
    {
      "name": "Renal Nutrition"
    },
    {
      "name": "Diabetes Nutrition"
    },
    {
      "name": "Fenton Preterm"
    },
    {
      "name": "FHIR"
    },
    {
      "name": "Packaged"
    },
    {
      "name": "Favorites"
    },
    {
      "name": "History"
    },
    {
      "name": "Food Log"
    },
    {
      "name": "Recipes"
    },
    {
      "name": "Meals"
    },
    {
      "name": "Dietary assessment"
    },
    {
      "name": "Paediatric nutrition"
    },
    {
      "name": "Critical care nutrition"
    },
    {
      "name": "Burn nutrition",
      "description": "Burn nutrition calculators (Parkland fluid, Toronto and pediatric energy equations, protein, nitrogen balance, micronutrients, glycaemic targets, Holliday-Segar, Weir, hypermetabolism drugs). Reference values are stored in clinical_reference_values, not coded."
    },
    {
      "name": "Refeeding syndrome",
      "description": "ASPEN consensus refeeding syndrome risk (adult and paediatric), severity and initiation guidance. Reference values are stored in clinical_reference_values (condition refeeding), not coded."
    },
    {
      "name": "Energy and stress factors",
      "description": "Harris-Benedict BEE with Barak et al (2002) clinical stress factors, 24-hour energy adjustment factors and the fever (Hardy and DuBois) adjustment. Reference values are stored in clinical_reference_values (condition stress_factors), not coded."
    },
    {
      "name": "Carbohydrate counting",
      "description": "Carbohydrate counting and mealtime insulin dose calculators and reference lists from a UK diabetes network patient-education guide (units per 10 g, mmol/L). Reference values are stored in clinical_reference_values (condition carb_counting), not coded. Separate from /diabetes."
    },
    {
      "name": "Enteral nutrition"
    },
    {
      "name": "Micronutrient assessment"
    },
    {
      "name": "Research engine"
    },
    {
      "name": "Longitudinal outcomes"
    },
    {
      "name": "Nutrition epidemiology"
    },
    {
      "name": "Data quality"
    },
    {
      "name": "Malnutrition research"
    },
    {
      "name": "Dataset export"
    },
    {
      "name": "Ingredients"
    },
    {
      "name": "DRI"
    },
    {
      "name": "RAG"
    },
    {
      "name": "Memory"
    }
  ],
  "paths": {
    "/": {
      "get": {
        "tags": [
          "Meta"
        ],
        "summary": "API metadata",
        "description": "Returns CNR metadata, version, auth summary, and a full endpoint map.",
        "security": [],
        "responses": {
          "200": {
            "description": "Metadata",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          }
        }
      }
    },
    "/health": {
      "get": {
        "tags": [
          "Health"
        ],
        "summary": "Upstream service health check",
        "description": "Pings Supabase, Cohere, Groq, and Open Food Facts in parallel. Reports per-service status plus an overall healthy/degraded verdict. Open Food Facts is live-pinged too (free/keyless) but, like USDA FDC/FatSecret/the rate-limit KV (reported as configured/bound without a network call), it's visibility-only and doesn't affect the overall verdict.\n",
        "security": [],
        "responses": {
          "200": {
            "description": "All required upstreams reachable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HealthStatus"
                }
              }
            }
          },
          "503": {
            "description": "One or more required upstreams (Supabase/Cohere/Groq) failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HealthStatus"
                }
              }
            }
          }
        }
      }
    },
    "/batch": {
      "post": {
        "tags": [
          "Batch"
        ],
        "summary": "Run several sub-requests in one round trip",
        "description": "Fans out to routeSingle() per sub-request - same auth, rate-limit, and edge-cache pipeline as a normal top-level request, so batching cannot bypass per-resource auth or rate limits. Nested /batch sub-requests are rejected. The batch endpoint has its own (tighter) rate limit. Results always come back in request order; one sub-request failing never short-circuits the rest.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "requests"
                ],
                "properties": {
                  "requests": {
                    "type": "array",
                    "minItems": 1,
                    "maxItems": 20,
                    "items": {
                      "type": "object",
                      "required": [
                        "method",
                        "path"
                      ],
                      "properties": {
                        "id": {
                          "type": "string",
                          "description": "Echoed back to match results to requests; defaults to array index."
                        },
                        "method": {
                          "type": "string",
                          "enum": [
                            "GET",
                            "POST",
                            "PUT",
                            "PATCH",
                            "DELETE"
                          ]
                        },
                        "path": {
                          "type": "string",
                          "example": "/foods/lookup?query=nsima"
                        },
                        "body": {
                          "type": "object",
                          "additionalProperties": true
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Per-item results, always in request order",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string",
                      "example": "success"
                    },
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string"
                          },
                          "status": {
                            "type": "integer"
                          },
                          "body": {}
                        }
                      }
                    },
                    "meta": {
                      "type": "object",
                      "properties": {
                        "total": {
                          "type": "integer"
                        },
                        "succeeded": {
                          "type": "integer"
                        },
                        "failed": {
                          "type": "integer"
                        },
                        "duration_ms": {
                          "type": "integer"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/admin/usage": {
      "get": {
        "tags": [
          "API Keys"
        ],
        "summary": "Usage by endpoint (root key only)",
        "description": "Which endpoints get called most. Calls are counted per UTC day, method and route (ids replaced, for example `/foods/:id`) in `route_usage`. Counts only - no keys, IPs, query strings or bodies. Skipped - OPTIONS, `/`, `/health` and `/admin/*`. When storage is not ready the response has `available` false and a note. Needs the root key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "days",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 7,
              "maximum": 30
            },
            "description": "Look-back window in UTC days, including today."
          }
        ],
        "responses": {
          "200": {
            "description": "Call counts per endpoint",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Success"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "object",
                          "properties": {
                            "generated_at": {
                              "type": "string",
                              "format": "date-time"
                            },
                            "window_days": {
                              "type": "integer"
                            },
                            "available": {
                              "type": "boolean"
                            },
                            "storage_enabled": {
                              "type": "boolean"
                            },
                            "truncated": {
                              "type": "boolean"
                            },
                            "totals": {
                              "type": "object",
                              "description": "calls, errors and routes (number of distinct endpoints used)."
                            },
                            "daily": {
                              "type": "array",
                              "items": {
                                "type": "object",
                                "description": "day and count, oldest first, zero-filled."
                              }
                            },
                            "top": {
                              "type": "array",
                              "description": "Up to 25 busiest endpoints.",
                              "items": {
                                "type": "object",
                                "description": "method, route, calls, errors, error_rate (percent), avg_ms, p95_ms (estimated from latency buckets, null until traffic arrives), share (percent of all calls) and last_day."
                              }
                            }
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Not the root key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/admin/health-history": {
      "get": {
        "tags": [
          "API Keys"
        ],
        "summary": "Uptime, upstream status and outage timeline (root key only)",
        "description": "Reads the health probe history. A cron runs the same checks as `GET /health` every 5 minutes and stores one row per run. Returns uptime percent, coverage (how many of the expected probes actually ran), per-upstream uptime and probe latency (average, p95, max), an outage timeline (runs of consecutive degraded probes, newest first) and daily uptime. `current.stale` is true when the latest probe is older than 15 minutes, which usually means the cron is not running. A probe that could not reach the database is held in KV and written with the next one, so a database outage still shows up. Only Supabase, Cohere and Groq count towards an outage. Needs the root key. When storage is not ready the response has `available` false and a note.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "hours",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 24,
              "maximum": 720
            },
            "description": "Look-back window in hours."
          }
        ],
        "responses": {
          "200": {
            "description": "Health history summary",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Success"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "object",
                          "properties": {
                            "generated_at": {
                              "type": "string",
                              "format": "date-time"
                            },
                            "window_hours": {
                              "type": "integer"
                            },
                            "available": {
                              "type": "boolean"
                            },
                            "storage_enabled": {
                              "type": "boolean"
                            },
                            "probe_every_minutes": {
                              "type": "integer"
                            },
                            "truncated": {
                              "type": "boolean"
                            },
                            "totals": {
                              "type": "object",
                              "description": "checks, healthy and degraded counts."
                            },
                            "uptime_pct": {
                              "type": "number",
                              "nullable": true,
                              "description": "Healthy probes as a percent of all probes in the window."
                            },
                            "coverage_pct": {
                              "type": "number",
                              "nullable": true,
                              "description": "Probes that ran as a percent of probes expected since the first one in the window."
                            },
                            "current": {
                              "type": "object",
                              "nullable": true,
                              "description": "Latest probe, with age_minutes, stale and each service status."
                            },
                            "services": {
                              "type": "object",
                              "description": "Per service checks, errors, uptime_pct, avg_ms, p95_ms, max_ms and required."
                            },
                            "incidents": {
                              "type": "array",
                              "items": {
                                "type": "object",
                                "description": "started_at, resolved_at, ongoing, checks, duration_minutes, failing_services and detail."
                              }
                            },
                            "daily": {
                              "type": "array",
                              "items": {
                                "type": "object",
                                "description": "day, checks, healthy and uptime_pct."
                              }
                            }
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Not the root key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/admin/monitoring": {
      "get": {
        "tags": [
          "API Keys"
        ],
        "summary": "Failed calls and slow endpoints (root key only)",
        "description": "Summary of failed and slow requests for the admin monitor. Only failures (5xx, 429 and 4xx other than 401 and 404) and slow calls (at or above `REQUEST_LOG_SLOW_MS`, default 1500 ms) are stored, so this shows what is breaking and what is slow, not an overall error rate. Rows hold the method, a route with ids replaced, status, duration and request id - never query strings, bodies, tokens or IPs. When storage is not ready the response has `available` false and a note. Needs the root key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "hours",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 24,
              "maximum": 168
            },
            "description": "Look-back window in hours."
          }
        ],
        "responses": {
          "200": {
            "description": "Failed and slow request summary",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Success"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "object",
                          "properties": {
                            "generated_at": {
                              "type": "string",
                              "format": "date-time"
                            },
                            "window_hours": {
                              "type": "integer"
                            },
                            "available": {
                              "type": "boolean"
                            },
                            "storage_enabled": {
                              "type": "boolean"
                            },
                            "slow_ms": {
                              "type": "integer"
                            },
                            "truncated": {
                              "type": "boolean"
                            },
                            "totals": {
                              "type": "object",
                              "description": "failed, slow and by_status counts."
                            },
                            "failing_endpoints": {
                              "type": "array",
                              "items": {
                                "type": "object",
                                "description": "method, route, count, statuses and last_at."
                              }
                            },
                            "slow_endpoints": {
                              "type": "array",
                              "items": {
                                "type": "object",
                                "description": "method, route, count, avg_ms, max_ms and last_at."
                              }
                            },
                            "recent": {
                              "type": "array",
                              "items": {
                                "type": "object",
                                "description": "at, request_id, method, route, status, duration_ms and kind."
                              }
                            }
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Not the root key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/admin/activity": {
      "get": {
        "tags": [
          "API Keys"
        ],
        "summary": "Recent admin actions (root key only)",
        "description": "Newest-first list of recent admin actions for the admin monitor page - API keys created and revoked, and packaged foods approved or rejected (with the reviewer and rejection reason). Built from existing tables, so nothing extra is stored. Key changes carry no actor because the API does not record one. Developer self-service keys are left out. Never returns a raw key or its hash. Needs the root key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 30,
              "maximum": 100
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Recent admin actions",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Success"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "object",
                          "properties": {
                            "generated_at": {
                              "type": "string",
                              "format": "date-time"
                            },
                            "sources": {
                              "type": "object",
                              "description": "Whether each source answered (api_keys, packaged_reviews)."
                            },
                            "events": {
                              "type": "array",
                              "items": {
                                "type": "object",
                                "properties": {
                                  "type": {
                                    "type": "string",
                                    "enum": [
                                      "key_created",
                                      "key_revoked",
                                      "packaged_approved",
                                      "packaged_rejected"
                                    ]
                                  },
                                  "at": {
                                    "type": "string",
                                    "format": "date-time"
                                  },
                                  "actor": {
                                    "type": "string",
                                    "nullable": true
                                  },
                                  "subject": {
                                    "type": "string"
                                  },
                                  "detail": {
                                    "type": "string"
                                  },
                                  "ref": {
                                    "type": "string"
                                  }
                                }
                              }
                            }
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Not the root key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/admin/overview": {
      "get": {
        "tags": [
          "API Keys"
        ],
        "summary": "Admin monitor snapshot (root key only)",
        "description": "Read-only snapshot for the admin monitor page - registry record counts, the packaged-food review queue (pending, approved, rejected), RAG ingest job counts by status, and API key and usage totals (active keys by role, usage today and over the last 7 days, daily totals and the top keys). Never returns a raw key or its hash. Needs the root key; admin, reviewer and consumer keys are refused.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Snapshot of registry, review queue, ingest jobs and key usage",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Success"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "object",
                          "properties": {
                            "registry": {
                              "type": "array",
                              "items": {
                                "type": "object",
                                "properties": {
                                  "label": {
                                    "type": "string"
                                  },
                                  "table": {
                                    "type": "string"
                                  },
                                  "count": {
                                    "type": "integer",
                                    "nullable": true
                                  }
                                }
                              }
                            },
                            "packaged_review": {
                              "type": "object",
                              "description": "Packaged-food counts by review status."
                            },
                            "ingest_jobs": {
                              "type": "object",
                              "description": "RAG ingest job counts keyed by queued, processing, done and failed."
                            },
                            "keys": {
                              "type": "object",
                              "description": "Key totals, counts by role, usage today and last 7 days, usage_daily and top keys."
                            }
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Not the root key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/admin/keys": {
      "get": {
        "tags": [
          "API Keys"
        ],
        "summary": "List API keys (root key only)",
        "description": "Never returns the raw key or its hash - id/label/timestamps only.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "List of keys",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Success"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/ApiKey"
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      },
      "post": {
        "tags": [
          "API Keys"
        ],
        "summary": "Create a new API key (root key only)",
        "description": "The raw key is returned exactly once in the response - only its SHA-256 hash is stored. If lost, revoke it and mint a new one.\n",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "label"
                ],
                "properties": {
                  "label": {
                    "type": "string",
                    "example": "Grace - reviewer"
                  },
                  "role": {
                    "type": "string",
                    "enum": [
                      "admin",
                      "reviewer"
                    ],
                    "default": "admin",
                    "description": "\"admin\" = full access (default). \"reviewer\" = packaged review queue (pending/approve/reject) + reads only.\n"
                  },
                  "fhir_scopes": {
                    "type": "string",
                    "description": "Optional space-separated FHIR scopes, e.g. \"system/Bundle.write system/*.read\". Defaults come from the role.",
                    "example": "system/*.read"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Key created",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Success"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "key": {
                          "type": "string",
                          "description": "Raw key - shown once only.",
                          "example": "cnr_9f2a1b...c81b"
                        },
                        "data": {
                          "$ref": "#/components/schemas/ApiKey"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/admin/keys/{id}": {
      "delete": {
        "tags": [
          "API Keys"
        ],
        "summary": "Revoke an API key (root key only)",
        "description": "Soft-revoke - sets `revoked_at`, the row is kept for audit history.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          }
        ],
        "responses": {
          "200": {
            "description": "Key revoked",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Success"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "$ref": "#/components/schemas/ApiKey"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/developer/keys": {
      "get": {
        "tags": [
          "Developer keys"
        ],
        "summary": "List your consumer API keys",
        "security": [
          {
            "loginToken": []
          }
        ],
        "description": "Your active keys with last-used time and usage counts. Authenticate with your login access token (the docs site account page does this for you). Never returns a raw key or its hash.",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string",
                      "example": "success"
                    },
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "integer"
                          },
                          "label": {
                            "type": "string"
                          },
                          "key_prefix": {
                            "type": "string",
                            "example": "cnr_1a2b3c4d"
                          },
                          "created_at": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "last_used_at": {
                            "type": "string",
                            "format": "date-time",
                            "nullable": true
                          },
                          "usage": {
                            "type": "object",
                            "properties": {
                              "today": {
                                "type": "integer"
                              },
                              "last_7_days": {
                                "type": "integer"
                              }
                            }
                          }
                        }
                      }
                    },
                    "email": {
                      "type": "string"
                    },
                    "max_keys": {
                      "type": "integer",
                      "example": 3
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "post": {
        "tags": [
          "Developer keys"
        ],
        "summary": "Create a consumer API key",
        "security": [
          {
            "loginToken": []
          }
        ],
        "description": "Creates a key with the `consumer` role. The raw key is returned once; only its hash is stored. Up to 3 active keys per account, and the account email must be confirmed. A consumer key gets 3x each route's rate limit (tracked per key) and FHIR scopes `system/*.*` (never AuditEvent). It can never reach admin routes or write data.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "label"
                ],
                "properties": {
                  "label": {
                    "type": "string",
                    "maxLength": 60,
                    "description": "The app or project this key is for.",
                    "example": "Thanzi mobile app"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string",
                      "example": "success"
                    },
                    "data": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "integer"
                        },
                        "label": {
                          "type": "string"
                        },
                        "key_prefix": {
                          "type": "string",
                          "example": "cnr_1a2b3c4d"
                        },
                        "created_at": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "last_used_at": {
                          "type": "string",
                          "format": "date-time",
                          "nullable": true
                        },
                        "usage": {
                          "type": "object",
                          "properties": {
                            "today": {
                              "type": "integer"
                            },
                            "last_7_days": {
                              "type": "integer"
                            }
                          }
                        }
                      }
                    },
                    "message": {
                      "type": "string"
                    },
                    "key": {
                      "type": "string",
                      "example": "cnr_0123...",
                      "description": "Shown once. Store it now."
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Email address not confirmed yet",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Already at the limit of 3 active keys",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/developer/keys/{id}": {
      "delete": {
        "tags": [
          "Developer keys"
        ],
        "summary": "Revoke one of your keys",
        "security": [
          {
            "loginToken": []
          }
        ],
        "description": "Revokes a key you own. Apps using it stop working immediately.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string",
                      "example": "success"
                    },
                    "data": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "integer"
                        }
                      }
                    },
                    "message": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/foods": {
      "get": {
        "tags": [
          "Foods"
        ],
        "summary": "List foods",
        "description": "With `search`, rows in the \"Baby foods\" category are left out (they sort ahead of ordinary foods, e.g. \"Baby food (jar), banana\" before \"Banana\"). They come back if the query has a baby word (baby, infant, toddler, weaning, nestum, cerelac, formula, puree), if `category=Baby foods`, if `include_baby_foods=true`, or if nothing else matches. To search only baby foods use `GET /foods/baby-foods`.\n",
        "parameters": [
          {
            "$ref": "#/components/parameters/Search"
          },
          {
            "name": "category",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "include_baby_foods",
            "in": "query",
            "description": "Set to true to include \"Baby foods\" rows in a name search.",
            "schema": {
              "type": "boolean"
            }
          },
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Offset"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/ListOrCursor"
          }
        }
      },
      "post": {
        "tags": [
          "Foods"
        ],
        "summary": "Create a food (admin)",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Food"
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/MutationSuccess"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/foods/bulk": {
      "post": {
        "tags": [
          "Foods"
        ],
        "summary": "Bulk-create foods (admin)",
        "description": "Batch insert, max 500 items per request, all-or-nothing (one PostgREST request - a constraint violation on any row rejects the whole batch).\n",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BulkRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/BulkSuccess"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/foods/lookup": {
      "get": {
        "tags": [
          "Foods"
        ],
        "summary": "External food lookup cascade (API key, rate-limited)",
        "description": "Local cache → USDA FDC (name search) → Open Food Facts (barcode) → FatSecret barcode lookup (Premier-exclusive) → FatSecret (name search). First external hit is cached into external_foods_cache so subsequent lookups skip upstream calls.\n",
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Name search (tries USDA, then FatSecret)."
          },
          {
            "name": "barcode",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Barcode lookup (Open Food Facts, then FatSecret)."
          },
          {
            "$ref": "#/components/parameters/Offset"
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/ListOrCursor"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/foods/autocomplete": {
      "get": {
        "tags": [
          "Foods"
        ],
        "summary": "Search-as-you-type suggestions (API key, rate-limited)",
        "description": "Wraps FatSecret's Premier-exclusive foods.autocomplete.v2. Requires FATSECRET_CONSUMER_KEY/SECRET on a Premier or Premier Free plan - returns 503 if not configured on this deployment.\n",
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Partial search expression, e.g. \"chic\"."
          },
          {
            "name": "max_results",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 4,
              "maximum": 10
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Suggested search expressions",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Success"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "description": "FatSecret credentials not configured on this deployment",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/foods/categories": {
      "get": {
        "tags": [
          "Foods"
        ],
        "summary": "Food category list (API key, rate-limited, cached 24h)",
        "description": "Wraps FatSecret's Premier-exclusive food_categories.get.v2 - near-static reference data. Requires FATSECRET_CONSUMER_KEY/SECRET on a Premier or Premier Free plan - returns 503 if not configured on this deployment.\n",
        "responses": {
          "200": {
            "description": "Food categories",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Success"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "id": {
                                "type": "string"
                              },
                              "name": {
                                "type": "string"
                              },
                              "description": {
                                "type": "string",
                                "nullable": true
                              }
                            }
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "description": "FatSecret credentials not configured on this deployment",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/foods/search": {
      "get": {
        "tags": [
          "Foods"
        ],
        "summary": "Typo-tolerant fuzzy search over foods (API key, rate-limited)",
        "description": "Postgres pg_trgm word_similarity() over food_name, so a misspelled Malawian food name (e.g. \"Chinagwa\" for \"Chinangwa\") still resolves - unlike GET /foods?search=, which requires an exact substring. Runs between the exact-ilike local match and the external cache/API cascade inside /foods/lookup and everything built on it.\n",
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "max_results",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 8,
              "minimum": 1,
              "maximum": 25
            }
          },
          {
            "name": "min_similarity",
            "in": "query",
            "description": "Per-word similarity floor, 0-1.",
            "schema": {
              "type": "number",
              "default": 0.35,
              "minimum": 0,
              "maximum": 1
            }
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/MutationSuccess"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/foods/substitutes": {
      "get": {
        "tags": [
          "Foods"
        ],
        "summary": "Nutritionally close substitutes for a food (API key, rate-limited)",
        "description": "Resolves the food (local match, falling back to the external lookup cascade), classifies it into a Malawi-relevant substitution group (e.g. protein/legumes are grouped together, cassava/potatoes with grains), and ranks other foods in that group by closeness on the group's primary nutrient per 100g. Purely computational, no writes. Doesn't account for cost, local availability, taste, or portion size.\n",
        "parameters": [
          {
            "name": "food_name",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "example": "chicken"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 5,
              "minimum": 1,
              "maximum": 15
            }
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/MutationSuccess"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/foods/compare": {
      "get": {
        "tags": [
          "Foods"
        ],
        "summary": "Side-by-side nutrient comparison of 2-6 foods (API key, rate-limited)",
        "description": "Resolves each comma-separated food name the same way /foods/substitutes resolves one, then returns each food's per-100g macro/micronutrient panel plus, only where a sourced row exists in glycaemic_index_data, a measured glycaemic block - GI/GL is never estimated. Also returns nutrient_comparison, the same fields pivoted per-nutrient with the highest/lowest food flagged.\n",
        "parameters": [
          {
            "name": "foods",
            "in": "query",
            "required": true,
            "description": "Comma-separated list of 2-6 food names.",
            "schema": {
              "type": "string",
              "example": "nsima,rice,potatoes"
            }
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/MutationSuccess"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/foods/convert": {
      "get": {
        "tags": [
          "Foods"
        ],
        "summary": "Household and standard measures for one food, either way round (API key, rate-limited)",
        "description": "Give a household measure (tsp, tbsp, dsp, h dsp, cup, us cup, scoop 16, or a food-specific label such as chipande, piece, slice) and get the standard weight; give a standard measure (g, kg, oz, lb, ml, l, fl oz) and get the household equivalents. The reply always has `standard`, `household` (the food's own servings as fractional counts, plus spoon and cup equivalents when the food has a density), the nutrients for that amount, and `assumptions`. A volume becomes grams using the food's own serving in that unit, else its density from a cup or spoon serving, else 1 g/ml (flagged). `cup` is the 250 ml metric household cup; `us cup` is 240 ml. Same food matching and gram conversion as POST /recipes/calculate.",
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "description": "Food name. Give this or `id`.",
            "schema": {
              "type": "string",
              "example": "rice"
            }
          },
          {
            "name": "id",
            "in": "query",
            "description": "Food id. Give this or `q`. Also available as GET /foods/{id}/convert.",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "amount",
            "in": "query",
            "description": "Quantity, default 1. Accepts 2, 0.5, 1/2, 1 1/2.",
            "schema": {
              "type": "string",
              "example": "2"
            }
          },
          {
            "name": "unit",
            "in": "query",
            "description": "Unit, default g. See GET /measures for every unit, or use a food-specific label (chipande, piece, slice).",
            "schema": {
              "type": "string",
              "example": "cup"
            }
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/MutationSuccess"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "description": "The unit could not be resolved for this food."
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/foods/{id}/convert": {
      "get": {
        "tags": [
          "Foods"
        ],
        "summary": "Household and standard measures for the food with this id (API key, rate-limited)",
        "description": "Same as GET /foods/convert with `id` in the path.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "amount",
            "in": "query",
            "schema": {
              "type": "string",
              "example": "300"
            }
          },
          {
            "name": "unit",
            "in": "query",
            "schema": {
              "type": "string",
              "example": "g"
            }
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/MutationSuccess"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "description": "The unit could not be resolved for this food."
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/measures": {
      "get": {
        "tags": [
          "Foods"
        ],
        "summary": "Every unit, the printed reference tables and the scoop sizes (API key, rate-limited)",
        "description": "Units with their size, aliases and source, the printed reference tables as published (UCT Household Measurements Guide, Kitchen Measurement Conversion Tables, Krause and Mahan 16th ed. Appendix 2), and the #6-#60 portion scoop sizes.",
        "parameters": [
          {
            "name": "section",
            "in": "query",
            "description": "Return one part only.",
            "schema": {
              "type": "string",
              "enum": [
                "units",
                "scoop_sizes",
                "reference_tables",
                "sources"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/MutationSuccess"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/measures/convert": {
      "get": {
        "tags": [
          "Foods"
        ],
        "summary": "Unit to unit, no food (API key, rate-limited)",
        "description": "Volume to volume or weight to weight. Leave out `to` for every unit of that kind. Weight-to-volume needs a food, so use GET /foods/convert for that.",
        "parameters": [
          {
            "name": "amount",
            "in": "query",
            "schema": {
              "type": "string",
              "example": "3"
            }
          },
          {
            "name": "from",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "example": "tbsp"
            }
          },
          {
            "name": "to",
            "in": "query",
            "schema": {
              "type": "string",
              "example": "ml"
            }
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/MutationSuccess"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "422": {
            "description": "The two units are of different kinds (volume vs weight)."
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/foods/baby-foods": {
      "get": {
        "tags": [
          "Foods"
        ],
        "summary": "Baby and infant foods (API key, rate-limited)",
        "description": "Only the \"Baby foods\" rows (infant cereals, jarred baby food). Same paging (`limit`, `offset`, `cursor`) and row shape as `GET /foods`, with an optional `search` on the food name.\n",
        "parameters": [
          {
            "$ref": "#/components/parameters/Search"
          },
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Offset"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/ListOrCursor"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/foods/local-categories": {
      "get": {
        "tags": [
          "Foods"
        ],
        "summary": "Categories used by the local food table (API key, rate-limited)",
        "description": "The category values the local Malawi food composition table actually uses (for example `Staples`, `Animal`, `Baby foods`), each with the number of foods in it. Pass these to `/foods/by-category` or to `category=` on `/foods` and `/foods/search`. This is a different list from `GET /foods/categories`, which returns FatSecret's taxonomy and does not match the local table.\n",
        "responses": {
          "200": {
            "description": "Local categories, sorted by name",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Success"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "name": {
                                "type": "string",
                                "example": "Staples"
                              },
                              "count": {
                                "type": "integer",
                                "example": 42
                              }
                            }
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/foods/by-category": {
      "get": {
        "tags": [
          "Foods"
        ],
        "summary": "Browse foods by category (API key, rate-limited)",
        "description": "Plain category browse over the local Malawi food composition table; no query text needed. A thin wrapper over the typo-tolerant search with the category filter applied. Each row is tagged `_source: foods` and `_match_type: category`.",
        "parameters": [
          {
            "name": "category",
            "in": "query",
            "required": true,
            "description": "Food category to list, a value from GET /foods/local-categories (matched case-insensitively).",
            "schema": {
              "type": "string",
              "example": "Staples"
            }
          },
          {
            "name": "max_results",
            "in": "query",
            "description": "Maximum rows to return (1-100, default 20).",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            }
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/MutationSuccess"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/foods/{id}/label": {
      "get": {
        "tags": [
          "Foods"
        ],
        "summary": "Codex-oriented nutrition label for one food",
        "description": "Standardized nutrition label built from Serving-Size Intelligence. References CXS 1-1985 (General Standard for the Labelling of Pre-packaged Foods) and CXG 2-1985 (Guidelines on Nutrition Labelling) separately as `labelling_standard` and `nutrition_labelling_guideline`. Every nutrition_declaration and vitamins_minerals_detail field carries an explicit status — `available`, `not_available`, or `estimated` — so a genuine 0 is never rendered the same as missing data, and `estimated` only appears where the source FCT's own data_quality_flags marks that value as assumed. `per_100g_reference` exposes the raw per-100g basis a scaled serving was derived from. Defaults to the most specific local serving available (chipande/mtanda, cup, tablespoon, handful, piece, etc.); an unrecognized `?serving=` falls back to the default rather than erroring, flagged via `requested_serving.matched=false`. All existing response fields (serving_size, calories, total_fat_g, saturated_fat_g, carbohydrates_g, fiber_g, sugars_g, protein_g, sodium_mg, vitamins_minerals, standard, missing_fields) are unchanged.\n",
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          },
          {
            "name": "serving",
            "in": "query",
            "description": "Case-insensitive substring match against a serving_sizes label (e.g. \"chipande\", \"mtanda\", \"100g\", \"cup\"). Omit to get the most specific serving Serving-Size Intelligence has for this food. An unmatched value falls back to the default and is flagged via requested_serving.matched=false rather than erroring.\n",
            "schema": {
              "type": "string"
            },
            "example": "chipande"
          }
        ],
        "responses": {
          "200": {
            "description": "Nutrition label built successfully",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string",
                      "example": "success"
                    },
                    "food_id": {
                      "type": "integer",
                      "example": 9
                    },
                    "food_name": {
                      "type": "string",
                      "example": "Cassava thick porridge, (Nsima ya kondowole)"
                    },
                    "serving_source": {
                      "type": "string",
                      "enum": [
                        "fct",
                        "local_intelligence",
                        "category_estimate",
                        "reference"
                      ],
                      "example": "local_intelligence"
                    },
                    "alternate_servings": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "label": {
                            "type": "string"
                          },
                          "grams": {
                            "type": "number"
                          },
                          "source": {
                            "type": "string"
                          }
                        }
                      }
                    },
                    "requested_serving": {
                      "type": "object",
                      "properties": {
                        "requested": {
                          "type": "string",
                          "nullable": true,
                          "example": "chipande"
                        },
                        "matched": {
                          "type": "boolean",
                          "example": true
                        },
                        "resolved_label": {
                          "type": "string",
                          "example": "1 chipande / mtanda (approx. 150-200g)"
                        }
                      }
                    },
                    "data": {
                      "type": "object",
                      "description": "The nutrition label itself.",
                      "properties": {
                        "serving_size": {
                          "type": "string",
                          "example": "1 chipande / mtanda (approx. 150-200g)"
                        },
                        "serving_grams": {
                          "type": "number",
                          "example": 175
                        },
                        "calories": {
                          "type": "number",
                          "nullable": true,
                          "example": 112
                        },
                        "total_fat_g": {
                          "type": "number",
                          "nullable": true,
                          "example": 0.7
                        },
                        "saturated_fat_g": {
                          "type": "number",
                          "nullable": true,
                          "example": null
                        },
                        "carbohydrates_g": {
                          "type": "number",
                          "nullable": true,
                          "example": 24.68
                        },
                        "fiber_g": {
                          "type": "number",
                          "nullable": true,
                          "example": 1.22
                        },
                        "sugars_g": {
                          "type": "number",
                          "nullable": true,
                          "example": null
                        },
                        "protein_g": {
                          "type": "number",
                          "nullable": true,
                          "example": 0.7
                        },
                        "sodium_mg": {
                          "type": "number",
                          "nullable": true,
                          "example": 8.75
                        },
                        "vitamins_minerals": {
                          "type": "object",
                          "description": "Legacy flat value map, unchanged",
                          "additionalProperties": {
                            "type": "number"
                          }
                        },
                        "standard": {
                          "type": "string",
                          "example": "codex_stan_1-1985",
                          "description": "Legacy field, unchanged"
                        },
                        "labelling_standard": {
                          "type": "string",
                          "example": "CXS 1-1985"
                        },
                        "nutrition_labelling_guideline": {
                          "type": "string",
                          "example": "CXG 2-1985"
                        },
                        "energy_kcal": {
                          "type": "number",
                          "nullable": true,
                          "example": 112
                        },
                        "energy_kj": {
                          "type": "number",
                          "nullable": true,
                          "example": 474.25
                        },
                        "nutrition_declaration": {
                          "type": "object",
                          "properties": {
                            "energy": {
                              "type": "object",
                              "properties": {
                                "kcal": {
                                  "type": "number",
                                  "nullable": true,
                                  "example": 112
                                },
                                "kj": {
                                  "type": "number",
                                  "nullable": true,
                                  "example": 474.25
                                },
                                "kcal_status": {
                                  "type": "string",
                                  "enum": [
                                    "available",
                                    "not_available",
                                    "estimated"
                                  ],
                                  "example": "available"
                                },
                                "kj_status": {
                                  "type": "string",
                                  "enum": [
                                    "available",
                                    "not_available",
                                    "estimated"
                                  ],
                                  "example": "available"
                                }
                              }
                            },
                            "protein_g": {
                              "type": "object",
                              "properties": {
                                "value": {
                                  "type": "number",
                                  "nullable": true,
                                  "example": 0.7
                                },
                                "status": {
                                  "type": "string",
                                  "enum": [
                                    "available",
                                    "not_available",
                                    "estimated"
                                  ]
                                }
                              }
                            },
                            "carbohydrate_g": {
                              "type": "object",
                              "properties": {
                                "value": {
                                  "type": "number",
                                  "nullable": true,
                                  "example": 24.68
                                },
                                "status": {
                                  "type": "string",
                                  "enum": [
                                    "available",
                                    "not_available",
                                    "estimated"
                                  ]
                                }
                              }
                            },
                            "total_sugars_g": {
                              "type": "object",
                              "properties": {
                                "value": {
                                  "type": "number",
                                  "nullable": true,
                                  "example": null
                                },
                                "status": {
                                  "type": "string",
                                  "enum": [
                                    "available",
                                    "not_available",
                                    "estimated"
                                  ],
                                  "example": "not_available"
                                }
                              }
                            },
                            "total_fat_g": {
                              "type": "object",
                              "properties": {
                                "value": {
                                  "type": "number",
                                  "nullable": true,
                                  "example": 0.7
                                },
                                "status": {
                                  "type": "string",
                                  "enum": [
                                    "available",
                                    "not_available",
                                    "estimated"
                                  ]
                                }
                              }
                            },
                            "saturated_fat_g": {
                              "type": "object",
                              "properties": {
                                "value": {
                                  "type": "number",
                                  "nullable": true,
                                  "example": null
                                },
                                "status": {
                                  "type": "string",
                                  "enum": [
                                    "available",
                                    "not_available",
                                    "estimated"
                                  ],
                                  "example": "not_available"
                                }
                              }
                            },
                            "dietary_fibre_g": {
                              "type": "object",
                              "properties": {
                                "value": {
                                  "type": "number",
                                  "nullable": true,
                                  "example": 1.22
                                },
                                "status": {
                                  "type": "string",
                                  "enum": [
                                    "available",
                                    "not_available",
                                    "estimated"
                                  ]
                                }
                              }
                            },
                            "sodium_mg": {
                              "type": "object",
                              "properties": {
                                "value": {
                                  "type": "number",
                                  "nullable": true,
                                  "example": 8.75
                                },
                                "status": {
                                  "type": "string",
                                  "enum": [
                                    "available",
                                    "not_available",
                                    "estimated"
                                  ]
                                }
                              }
                            }
                          }
                        },
                        "vitamins_minerals_detail": {
                          "type": "object",
                          "description": "Same nutrients as vitamins_minerals, each paired with a status so a value of 0 is never confused with missing data (e.g. Vitamin D at 0 mcg but status \"estimated\" because the source FCT flagged it as assumed, not measured).\n",
                          "additionalProperties": {
                            "type": "object",
                            "properties": {
                              "value": {
                                "type": "number",
                                "nullable": true
                              },
                              "status": {
                                "type": "string",
                                "enum": [
                                  "available",
                                  "not_available",
                                  "estimated"
                                ]
                              }
                            }
                          }
                        },
                        "calculation": {
                          "type": "object",
                          "properties": {
                            "serving_weight_grams": {
                              "type": "number",
                              "example": 175
                            },
                            "serving_weight_source": {
                              "type": "string",
                              "enum": [
                                "measured",
                                "estimated"
                              ],
                              "nullable": true,
                              "example": "estimated"
                            },
                            "serving_weight_basis": {
                              "type": "string",
                              "enum": [
                                "fct",
                                "local_intelligence",
                                "category_estimate",
                                "reference"
                              ],
                              "example": "local_intelligence"
                            },
                            "value_basis": {
                              "type": "string",
                              "enum": [
                                "direct_from_database",
                                "calculated_from_per_100g_reference"
                              ],
                              "example": "calculated_from_per_100g_reference"
                            }
                          }
                        },
                        "per_100g_reference": {
                          "type": "object",
                          "nullable": true,
                          "description": "The raw per-100g basis the selected serving was scaled from.",
                          "properties": {
                            "calories": {
                              "type": "number",
                              "nullable": true,
                              "example": 64
                            },
                            "energy_kj": {
                              "type": "number",
                              "nullable": true,
                              "example": 271
                            },
                            "total_fat_g": {
                              "type": "number",
                              "nullable": true,
                              "example": 0.4
                            },
                            "saturated_fat_g": {
                              "type": "number",
                              "nullable": true,
                              "example": null
                            },
                            "carbohydrates_g": {
                              "type": "number",
                              "nullable": true,
                              "example": 14.1
                            },
                            "fiber_g": {
                              "type": "number",
                              "nullable": true,
                              "example": 0.7
                            },
                            "sugars_g": {
                              "type": "number",
                              "nullable": true,
                              "example": null
                            },
                            "protein_g": {
                              "type": "number",
                              "nullable": true,
                              "example": 0.4
                            },
                            "sodium_mg": {
                              "type": "number",
                              "nullable": true,
                              "example": 5
                            },
                            "vitamins_minerals": {
                              "type": "object",
                              "additionalProperties": {
                                "type": "number"
                              }
                            }
                          }
                        },
                        "missing_fields": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          },
                          "example": [
                            "saturated_fat_g",
                            "sugars_g"
                          ]
                        }
                      }
                    }
                  }
                },
                "example": {
                  "status": "success",
                  "food_id": 9,
                  "food_name": "Cassava thick porridge, (Nsima ya kondowole)",
                  "serving_source": "local_intelligence",
                  "alternate_servings": [
                    {
                      "label": "100g",
                      "grams": 100,
                      "source": "fct"
                    }
                  ],
                  "requested_serving": {
                    "requested": "chipande",
                    "matched": true,
                    "resolved_label": "1 chipande / mtanda (approx. 150-200g)"
                  },
                  "data": {
                    "serving_size": "1 chipande / mtanda (approx. 150-200g)",
                    "serving_grams": 175,
                    "calories": 112,
                    "total_fat_g": 0.7,
                    "saturated_fat_g": null,
                    "carbohydrates_g": 24.68,
                    "fiber_g": 1.22,
                    "sugars_g": null,
                    "protein_g": 0.7,
                    "sodium_mg": 8.75,
                    "vitamins_minerals": {
                      "Vitamin C (mg)": 1.75,
                      "Vitamin D (mcg)": 0,
                      "Vitamin B12 (mcg)": 0
                    },
                    "standard": "codex_stan_1-1985",
                    "labelling_standard": "CXS 1-1985",
                    "nutrition_labelling_guideline": "CXG 2-1985",
                    "energy_kcal": 112,
                    "energy_kj": 474.25,
                    "nutrition_declaration": {
                      "energy": {
                        "kcal": 112,
                        "kj": 474.25,
                        "kcal_status": "available",
                        "kj_status": "available"
                      },
                      "protein_g": {
                        "value": 0.7,
                        "status": "available"
                      },
                      "carbohydrate_g": {
                        "value": 24.68,
                        "status": "available"
                      },
                      "total_sugars_g": {
                        "value": null,
                        "status": "not_available"
                      },
                      "total_fat_g": {
                        "value": 0.7,
                        "status": "available"
                      },
                      "saturated_fat_g": {
                        "value": null,
                        "status": "not_available"
                      },
                      "dietary_fibre_g": {
                        "value": 1.22,
                        "status": "available"
                      },
                      "sodium_mg": {
                        "value": 8.75,
                        "status": "available"
                      }
                    },
                    "vitamins_minerals_detail": {
                      "Vitamin D (mcg)": {
                        "value": 0,
                        "status": "estimated"
                      },
                      "Vitamin B12 (mcg)": {
                        "value": 0,
                        "status": "estimated"
                      },
                      "Vitamin C (mg)": {
                        "value": 1.75,
                        "status": "available"
                      }
                    },
                    "calculation": {
                      "serving_weight_grams": 175,
                      "serving_weight_source": "estimated",
                      "serving_weight_basis": "local_intelligence",
                      "value_basis": "calculated_from_per_100g_reference"
                    },
                    "per_100g_reference": {
                      "calories": 64,
                      "energy_kj": 271,
                      "total_fat_g": 0.4,
                      "carbohydrates_g": 14.1,
                      "fiber_g": 0.7,
                      "protein_g": 0.4,
                      "sodium_mg": 5
                    },
                    "missing_fields": [
                      "saturated_fat_g",
                      "sugars_g"
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "description": "No serving-size data available, or an invalid (non-positive) serving weight",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string",
                      "example": "error"
                    },
                    "message": {
                      "type": "string",
                      "example": "No serving-size data available to build a label from"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/foods/{id}": {
      "get": {
        "tags": [
          "Foods"
        ],
        "summary": "Get a food by id",
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/MutationSuccess"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "put": {
        "tags": [
          "Foods"
        ],
        "summary": "Replace a food (admin)",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Food"
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/MutationSuccess"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "patch": {
        "tags": [
          "Foods"
        ],
        "summary": "Partially update a food (admin)",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Food"
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/MutationSuccess"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "delete": {
        "tags": [
          "Foods"
        ],
        "summary": "Delete a food (admin)",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/MutationSuccess"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/glycaemic-index": {
      "get": {
        "tags": [
          "Glycaemic Index"
        ],
        "summary": "List glycaemic index/load reference entries",
        "description": "Standalone, source-cited GI/GL reference table (sql/004_add_glycaemic_index.sql). Matched by keyword at query time (see GET /foods/compare and /rag/ask) rather than joined to `foods` by foreign key, since GI/GL is measured per specific preparation/variety and rarely lines up 1:1 with a foods row.\n",
        "parameters": [
          {
            "$ref": "#/components/parameters/Search"
          },
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Offset"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/ListOrCursor"
          }
        }
      },
      "post": {
        "tags": [
          "Glycaemic Index"
        ],
        "summary": "Create a glycaemic index entry (admin)",
        "description": "Requires food_name, a non-empty match_keywords array, and source - GI/GL values are never accepted without a traceable citation.\n",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "food_name",
                  "match_keywords",
                  "source"
                ],
                "properties": {
                  "food_name": {
                    "type": "string"
                  },
                  "match_keywords": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "source": {
                    "type": "string"
                  }
                },
                "additionalProperties": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/MutationSuccess"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/glycaemic-index/bulk": {
      "post": {
        "tags": [
          "Glycaemic Index"
        ],
        "summary": "Bulk-create glycaemic index entries (admin)",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BulkRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/BulkSuccess"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/glycaemic-index/{id}": {
      "get": {
        "tags": [
          "Glycaemic Index"
        ],
        "summary": "Get a glycaemic index entry by id",
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/MutationSuccess"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "put": {
        "tags": [
          "Glycaemic Index"
        ],
        "summary": "Replace a glycaemic index entry (admin)",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/MutationSuccess"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      },
      "patch": {
        "tags": [
          "Glycaemic Index"
        ],
        "summary": "Partially update a glycaemic index entry (admin)",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/MutationSuccess"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      },
      "delete": {
        "tags": [
          "Glycaemic Index"
        ],
        "summary": "Delete a glycaemic index entry (admin)",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/MutationSuccess"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/exchange": {
      "get": {
        "tags": [
          "Exchange"
        ],
        "summary": "List diabetes/renal exchange list entries",
        "parameters": [
          {
            "name": "type",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Offset"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/ListOrCursor"
          }
        }
      },
      "post": {
        "tags": [
          "Exchange"
        ],
        "summary": "Create an exchange list entry (admin)",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/MutationSuccess"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/exchange/bulk": {
      "post": {
        "tags": [
          "Exchange"
        ],
        "summary": "Bulk-create exchange list entries (admin)",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BulkRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/BulkSuccess"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/exchange/{id}": {
      "put": {
        "tags": [
          "Exchange"
        ],
        "summary": "Replace an exchange list entry (admin)",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/MutationSuccess"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      },
      "patch": {
        "tags": [
          "Exchange"
        ],
        "summary": "Partially update an exchange list entry (admin)",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/MutationSuccess"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      },
      "delete": {
        "tags": [
          "Exchange"
        ],
        "summary": "Delete an exchange list entry (admin)",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/MutationSuccess"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/renal": {
      "get": {
        "tags": [
          "Renal"
        ],
        "summary": "List renal foods",
        "parameters": [
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Offset"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/ListOrCursor"
          }
        }
      },
      "post": {
        "tags": [
          "Renal"
        ],
        "summary": "Create a renal food entry (admin)",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/MutationSuccess"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/renal/bulk": {
      "post": {
        "tags": [
          "Renal"
        ],
        "summary": "Bulk-create renal food entries (admin)",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BulkRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/BulkSuccess"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/renal/{id}": {
      "put": {
        "tags": [
          "Renal"
        ],
        "summary": "Replace a renal food entry (admin)",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/MutationSuccess"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      },
      "patch": {
        "tags": [
          "Renal"
        ],
        "summary": "Partially update a renal food entry (admin)",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/MutationSuccess"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      },
      "delete": {
        "tags": [
          "Renal"
        ],
        "summary": "Delete a renal food entry (admin)",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/MutationSuccess"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/formulas": {
      "get": {
        "tags": [
          "Formulas"
        ],
        "summary": "List enteral formulas",
        "parameters": [
          {
            "name": "route",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Offset"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/ListOrCursor"
          }
        }
      },
      "post": {
        "tags": [
          "Formulas"
        ],
        "summary": "Create an enteral formula (admin)",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/MutationSuccess"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/formulas/bulk": {
      "post": {
        "tags": [
          "Formulas"
        ],
        "summary": "Bulk-create enteral formulas (admin)",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BulkRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/BulkSuccess"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/formulas/{id}": {
      "put": {
        "tags": [
          "Formulas"
        ],
        "summary": "Replace an enteral formula (admin)",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/MutationSuccess"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      },
      "patch": {
        "tags": [
          "Formulas"
        ],
        "summary": "Partially update an enteral formula (admin)",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/MutationSuccess"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      },
      "delete": {
        "tags": [
          "Formulas"
        ],
        "summary": "Delete an enteral formula (admin)",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/MutationSuccess"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/drug-interactions": {
      "get": {
        "tags": [
          "Drug Interactions"
        ],
        "summary": "List drug-nutrient interaction entries",
        "parameters": [
          {
            "name": "category",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "severity",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Offset"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/ListOrCursor"
          }
        }
      },
      "post": {
        "tags": [
          "Drug Interactions"
        ],
        "summary": "Create a drug-nutrient interaction entry (admin)",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "drug"
                ],
                "properties": {
                  "drug": {
                    "type": "string"
                  }
                },
                "additionalProperties": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/MutationSuccess"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/drug-interactions/bulk": {
      "post": {
        "tags": [
          "Drug Interactions"
        ],
        "summary": "Bulk-create drug-nutrient interaction entries (admin)",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BulkRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/BulkSuccess"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/drug-interactions/search": {
      "get": {
        "tags": [
          "Drug Interactions"
        ],
        "summary": "Keyword search across drug-nutrient interactions",
        "description": "Whole-row keyword scan (no single indexed \"name\" column) - `q` can be a drug name, brand name, drug class, or a nutrient/food keyword (e.g. \"grapefruit\", \"warfarin\", \"vitamin B12\"). Mirrors the client-side search this data previously had in Oasis CNST.\n",
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "example": "warfarin"
            }
          },
          {
            "name": "scan_limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 300
            }
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/MutationSuccess"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          }
        }
      }
    },
    "/drug-interactions/{id}": {
      "get": {
        "tags": [
          "Drug Interactions"
        ],
        "summary": "Get a drug-nutrient interaction entry by id",
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/MutationSuccess"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "put": {
        "tags": [
          "Drug Interactions"
        ],
        "summary": "Replace a drug-nutrient interaction entry (admin)",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/MutationSuccess"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      },
      "patch": {
        "tags": [
          "Drug Interactions"
        ],
        "summary": "Partially update a drug-nutrient interaction entry (admin)",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/MutationSuccess"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      },
      "delete": {
        "tags": [
          "Drug Interactions"
        ],
        "summary": "Delete a drug-nutrient interaction entry (admin)",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/MutationSuccess"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/bmi-for-age/classify": {
      "get": {
        "tags": [
          "BMI for age"
        ],
        "summary": "Classify BMI-for-age for a child or adolescent (5y 1m to 19y 0m)",
        "description": "WHO 2007 BMI-for-age reference as printed in the Malawi MoH guide Eat Well to Live Well (2021), Annex 2. Pass either bmi, or both weight_kg and height_cm. Compared against the unrounded BMI with strict cut-offs: severe thinness below -3SD, thinness below -2SD, normal from -2SD to +1SD, overweight above +1SD, obesity above +2SD. Screening aid only, not a diagnosis.\n",
        "parameters": [
          {
            "name": "sex",
            "in": "query",
            "required": true,
            "description": "girls or boys (girl, boy, female, male, f, m also accepted)",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "age_months",
            "in": "query",
            "required": true,
            "description": "Whole months, years x 12 + months (7y 11m = 95)",
            "schema": {
              "type": "integer",
              "minimum": 61,
              "maximum": 228
            }
          },
          {
            "name": "bmi",
            "in": "query",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "weight_kg",
            "in": "query",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "height_cm",
            "in": "query",
            "schema": {
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Classification",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Success"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "object",
                          "properties": {
                            "sex": {
                              "type": "string",
                              "enum": [
                                "girls",
                                "boys"
                              ]
                            },
                            "age_months": {
                              "type": "integer"
                            },
                            "status": {
                              "type": "string",
                              "enum": [
                                "severe thinness",
                                "thinness",
                                "normal",
                                "overweight",
                                "obesity"
                              ]
                            },
                            "bmi": {
                              "type": "number"
                            },
                            "cutoffs": {
                              "type": "object",
                              "additionalProperties": {
                                "type": "number"
                              },
                              "description": "Keys -3SD, -2SD, -1SD, median, +1SD, +2SD, +3SD"
                            },
                            "source": {
                              "type": "string"
                            },
                            "note": {
                              "type": "string"
                            }
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/bmi-for-age": {
      "get": {
        "tags": [
          "BMI for age"
        ],
        "summary": "List BMI-for-age reference rows (-3SD to +3SD cut-offs)",
        "parameters": [
          {
            "name": "sex",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "age_months",
            "in": "query",
            "schema": {
              "type": "integer"
            }
          },
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Offset"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/ListOrCursor"
          }
        }
      }
    },
    "/bmi-for-age/bulk": {
      "post": {
        "tags": [
          "BMI for age"
        ],
        "summary": "Bulk-create BMI-for-age rows (admin)",
        "description": "Seed file scripts/bmi_for_age_seed.json (336 rows). A unique (sex, age_months) constraint rejects re-seeding.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BulkRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/BulkSuccess"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/hc-velocity/classify": {
      "get": {
        "tags": [
          "HC velocity"
        ],
        "summary": "Classify a 6-month head circumference increment (birth to 24 months)",
        "description": "WHO Child Growth Standards 6-month head circumference increment (simplified field tables, -3 SD to +3 SD). This is head growth VELOCITY, not head circumference for age. Pass the start age of the 6-month window, plus either increment_cm or both hc_start_cm and hc_end_cm. Strict cut-offs: very low below -3SD, low below -2SD, within expected range from -2SD to +2SD, high above +2SD, very high above +3SD. z_approx is interpolated between the printed SD points (null beyond -3 or +3). Screening aid only, not a diagnosis.\n",
        "parameters": [
          {
            "name": "sex",
            "in": "query",
            "required": true,
            "description": "girls or boys (girl, boy, female, male, f, m also accepted)",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "start_age_months",
            "in": "query",
            "required": true,
            "description": "Whole months at the first measurement; window is start to start+6 (3 means the 3-9 month window)",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "maximum": 18
            }
          },
          {
            "name": "end_age_months",
            "in": "query",
            "description": "Optional; must equal start_age_months + 6",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "increment_cm",
            "in": "query",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "hc_start_cm",
            "in": "query",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "hc_end_cm",
            "in": "query",
            "schema": {
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Classification",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Success"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "object",
                          "properties": {
                            "sex": {
                              "type": "string",
                              "enum": [
                                "girls",
                                "boys"
                              ]
                            },
                            "interval": {
                              "type": "string",
                              "example": "3-9 months"
                            },
                            "interval_start_months": {
                              "type": "integer"
                            },
                            "interval_end_months": {
                              "type": "integer"
                            },
                            "status": {
                              "type": "string",
                              "enum": [
                                "very low increment",
                                "low increment",
                                "within expected range",
                                "high increment",
                                "very high increment"
                              ]
                            },
                            "increment_cm": {
                              "type": "number"
                            },
                            "z_approx": {
                              "type": "number",
                              "nullable": true
                            },
                            "cutoffs": {
                              "type": "object",
                              "additionalProperties": {
                                "type": "number"
                              },
                              "description": "Keys -3SD, -2SD, -1SD, median, +1SD, +2SD, +3SD"
                            },
                            "source": {
                              "type": "string"
                            },
                            "note": {
                              "type": "string"
                            }
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/hc-velocity": {
      "get": {
        "tags": [
          "HC velocity"
        ],
        "summary": "List WHO 6-month head circumference increment rows (-3SD to +3SD, cm)",
        "parameters": [
          {
            "name": "sex",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "start_age_months",
            "in": "query",
            "schema": {
              "type": "integer"
            }
          },
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Offset"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/ListOrCursor"
          }
        }
      }
    },
    "/hc-velocity/bulk": {
      "post": {
        "tags": [
          "HC velocity"
        ],
        "summary": "Bulk-create HC velocity rows (admin)",
        "description": "Seed file scripts/who_hc_velocity_seed.json (38 rows). A unique (sex, interval_start_months) constraint rejects re-seeding.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BulkRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/BulkSuccess"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/length-velocity/classify": {
      "get": {
        "tags": [
          "Length velocity"
        ],
        "summary": "Classify a 6-month length increment (birth to 24 months)",
        "description": "WHO Child Growth Standards 6-month length increment (simplified field tables, -3 SD to +3 SD). This is length VELOCITY, not length for age. Pass the start age of the 6-month window, plus either increment_cm or both length_start_cm and length_end_cm. Strict cut-offs: very low below -3SD, low below -2SD, within expected range from -2SD to +2SD, high above +2SD, very high above +3SD. z_approx is interpolated between the printed SD points (null beyond -3 or +3). Screening aid only, not a diagnosis.\n",
        "parameters": [
          {
            "name": "sex",
            "in": "query",
            "required": true,
            "description": "girls or boys (girl, boy, female, male, f, m also accepted)",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "start_age_months",
            "in": "query",
            "required": true,
            "description": "Whole months at the first measurement; window is start to start+6 (3 means the 3-9 month window)",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "maximum": 18
            }
          },
          {
            "name": "end_age_months",
            "in": "query",
            "description": "Optional; must equal start_age_months + 6",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "increment_cm",
            "in": "query",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "length_start_cm",
            "in": "query",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "length_end_cm",
            "in": "query",
            "schema": {
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Classification",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Success"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "object",
                          "properties": {
                            "sex": {
                              "type": "string",
                              "enum": [
                                "girls",
                                "boys"
                              ]
                            },
                            "interval": {
                              "type": "string",
                              "example": "3-9 months"
                            },
                            "interval_start_months": {
                              "type": "integer"
                            },
                            "interval_end_months": {
                              "type": "integer"
                            },
                            "status": {
                              "type": "string",
                              "enum": [
                                "very low increment",
                                "low increment",
                                "within expected range",
                                "high increment",
                                "very high increment"
                              ]
                            },
                            "increment_cm": {
                              "type": "number"
                            },
                            "z_approx": {
                              "type": "number",
                              "nullable": true
                            },
                            "cutoffs": {
                              "type": "object",
                              "additionalProperties": {
                                "type": "number"
                              },
                              "description": "Keys -3SD, -2SD, -1SD, median, +1SD, +2SD, +3SD"
                            },
                            "source": {
                              "type": "string"
                            },
                            "note": {
                              "type": "string"
                            }
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/length-velocity": {
      "get": {
        "tags": [
          "Length velocity"
        ],
        "summary": "List WHO 6-month length increment rows (-3SD to +3SD, cm)",
        "parameters": [
          {
            "name": "sex",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "start_age_months",
            "in": "query",
            "schema": {
              "type": "integer"
            }
          },
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Offset"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/ListOrCursor"
          }
        }
      }
    },
    "/length-velocity/bulk": {
      "post": {
        "tags": [
          "Length velocity"
        ],
        "summary": "Bulk-create Length velocity rows (admin)",
        "description": "Seed file scripts/who_length_velocity_seed.json (38 rows). A unique (sex, interval_start_months) constraint rejects re-seeding.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BulkRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/BulkSuccess"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/weight-velocity/classify": {
      "get": {
        "tags": [
          "Weight velocity"
        ],
        "summary": "Classify a 6-month weight increment (birth to 24 months)",
        "description": "WHO Child Growth Standards 6-month weight increment (grams, simplified field tables). This is weight VELOCITY, not weight for age. Pass the start age of the 6-month window, plus either increment_g (grams) or both weight_start_kg and weight_end_kg (kilograms). Strict cut-offs: very low below -3SD, low below -2SD, within expected range from -2SD to +2SD, high above +2SD, very high above +3SD. z_score and percentile are exact, computed from the WHO L, M, S and Delta parameters (null if the shifted increment is not positive). Screening aid only, not a diagnosis.\n",
        "parameters": [
          {
            "name": "sex",
            "in": "query",
            "required": true,
            "description": "girls or boys (girl, boy, female, male, f, m also accepted)",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "start_age_months",
            "in": "query",
            "required": true,
            "description": "Whole months at the first weighing; window is start to start+6 (3 means the 3-9 month window)",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "maximum": 18
            }
          },
          {
            "name": "end_age_months",
            "in": "query",
            "description": "Optional; must equal start_age_months + 6",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "increment_g",
            "in": "query",
            "description": "Weight gain over the window in GRAMS",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "weight_start_kg",
            "in": "query",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "weight_end_kg",
            "in": "query",
            "schema": {
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Classification",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Success"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "object",
                          "properties": {
                            "sex": {
                              "type": "string",
                              "enum": [
                                "girls",
                                "boys"
                              ]
                            },
                            "interval": {
                              "type": "string",
                              "example": "3-9 months"
                            },
                            "interval_start_months": {
                              "type": "integer"
                            },
                            "interval_end_months": {
                              "type": "integer"
                            },
                            "status": {
                              "type": "string",
                              "enum": [
                                "very low increment",
                                "low increment",
                                "within expected range",
                                "high increment",
                                "very high increment"
                              ]
                            },
                            "increment_g": {
                              "type": "number"
                            },
                            "z_score": {
                              "type": "number",
                              "nullable": true
                            },
                            "percentile": {
                              "type": "number",
                              "nullable": true
                            },
                            "cutoffs": {
                              "type": "object",
                              "additionalProperties": {
                                "type": "number"
                              },
                              "description": "Keys -3SD, -2SD, -1SD, median, +1SD, +2SD, +3SD (grams)"
                            },
                            "source": {
                              "type": "string"
                            },
                            "note": {
                              "type": "string"
                            }
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/weight-velocity": {
      "get": {
        "tags": [
          "Weight velocity"
        ],
        "summary": "List WHO 6-month weight increment rows (grams, with L/M/S/Delta)",
        "parameters": [
          {
            "name": "sex",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "start_age_months",
            "in": "query",
            "schema": {
              "type": "integer"
            }
          },
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Offset"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/ListOrCursor"
          }
        }
      }
    },
    "/weight-velocity/bulk": {
      "post": {
        "tags": [
          "Weight velocity"
        ],
        "summary": "Bulk-create weight velocity rows (admin)",
        "description": "Seed file scripts/who_weight_velocity_seed.json (38 rows). A unique (sex, interval_start_months) constraint rejects re-seeding.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BulkRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/BulkSuccess"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/muac-for-age/classify": {
      "get": {
        "tags": [
          "MUAC for age"
        ],
        "summary": "Classify arm circumference (MUAC) for age, 3 to 60 months",
        "description": "WHO Child Growth Standards arm circumference-for-age (cm) as a z-score for age. Not the fixed-cut-off MUAC tape (115 / 125 mm). Pass sex, age_months and either muac_cm or muac_mm. Strict cut-offs: very low below -3SD, low below -2SD, within expected range from -2SD to +2SD, high above +2SD, very high above +3SD. z_score and percentile are exact, computed from the WHO L, M and S values (linear extension beyond +/-3SD, as in the WHO tables). Screening aid only, not a diagnosis.\n",
        "parameters": [
          {
            "name": "sex",
            "in": "query",
            "required": true,
            "description": "girls or boys (girl, boy, female, male, f, m also accepted)",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "age_months",
            "in": "query",
            "required": true,
            "description": "Whole completed months",
            "schema": {
              "type": "integer",
              "minimum": 3,
              "maximum": 60
            }
          },
          {
            "name": "muac_cm",
            "in": "query",
            "description": "Mid-upper arm circumference in CENTIMETRES",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "muac_mm",
            "in": "query",
            "description": "Mid-upper arm circumference in MILLIMETRES (used if muac_cm is absent)",
            "schema": {
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Classification",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Success"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "object",
                          "properties": {
                            "sex": {
                              "type": "string",
                              "enum": [
                                "girls",
                                "boys"
                              ]
                            },
                            "age_months": {
                              "type": "integer"
                            },
                            "status": {
                              "type": "string",
                              "enum": [
                                "very low",
                                "low",
                                "within expected range",
                                "high",
                                "very high"
                              ]
                            },
                            "muac_cm": {
                              "type": "number"
                            },
                            "z_score": {
                              "type": "number",
                              "nullable": true
                            },
                            "percentile": {
                              "type": "number",
                              "nullable": true
                            },
                            "cutoffs": {
                              "type": "object",
                              "additionalProperties": {
                                "type": "number"
                              },
                              "description": "Keys -3SD, -2SD, -1SD, median, +1SD, +2SD, +3SD (cm)"
                            },
                            "source": {
                              "type": "string"
                            },
                            "note": {
                              "type": "string"
                            }
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/muac-for-age": {
      "get": {
        "tags": [
          "MUAC for age"
        ],
        "summary": "List WHO arm circumference-for-age rows (cm, with L/M/S)",
        "parameters": [
          {
            "name": "sex",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "age_months",
            "in": "query",
            "schema": {
              "type": "integer"
            }
          },
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Offset"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/ListOrCursor"
          }
        }
      }
    },
    "/muac-for-age/bulk": {
      "post": {
        "tags": [
          "MUAC for age"
        ],
        "summary": "Bulk-create MUAC-for-age rows (admin)",
        "description": "Seed file scripts/muac_for_age_seed.json. A unique (sex, age_months) constraint rejects re-seeding of months already present.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BulkRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/BulkSuccess"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/muac/classify": {
      "get": {
        "tags": [
          "MUAC fixed cut-offs"
        ],
        "summary": "Classify MUAC with fixed age-group cut-offs (6 months to adult)",
        "description": "Severe / moderate / normal by age group (WHO 2008 HIV paediatric guideline, as reproduced by FANTA Ethiopia). Pure calculation, no table. Moderate includes both end values. For 6-59 months these differ from the WHO/UNICEF 115 / 125 mm community screening cut-offs. Screening aid only, not a diagnosis.\n",
        "parameters": [
          {
            "name": "age_months",
            "in": "query",
            "required": true,
            "description": "Whole completed months (6 or more; adults are 180 or more)",
            "schema": {
              "type": "integer",
              "minimum": 6
            }
          },
          {
            "name": "muac_cm",
            "in": "query",
            "description": "MUAC in CENTIMETRES",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "muac_mm",
            "in": "query",
            "description": "MUAC in MILLIMETRES (used if muac_cm is absent)",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "scheme",
            "in": "query",
            "description": "who_hiv_2008 (default; chart by age group, 6 months to adult) or cmam (WHO/UNICEF 115 / 125 mm community cut-offs, 6-59 months only; 12.5 cm itself is normal)\n",
            "schema": {
              "type": "string",
              "enum": [
                "who_hiv_2008",
                "cmam"
              ],
              "default": "who_hiv_2008"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Classification",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Success"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "object",
                          "properties": {
                            "age_months": {
                              "type": "integer"
                            },
                            "scheme": {
                              "type": "string",
                              "enum": [
                                "who_hiv_2008",
                                "cmam"
                              ]
                            },
                            "age_group": {
                              "type": "string",
                              "enum": [
                                "6-11 months",
                                "12-59 months",
                                "5-9 years",
                                "10-14 years",
                                "adults"
                              ]
                            },
                            "status": {
                              "type": "string",
                              "enum": [
                                "severe",
                                "moderate",
                                "normal"
                              ]
                            },
                            "muac_cm": {
                              "type": "number"
                            },
                            "cutoffs_cm": {
                              "type": "object",
                              "properties": {
                                "severe_below": {
                                  "type": "number"
                                },
                                "normal_above": {
                                  "type": "number"
                                }
                              }
                            },
                            "source": {
                              "type": "string"
                            },
                            "note": {
                              "type": "string"
                            }
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/length-height-for-age/classify": {
      "get": {
        "tags": [
          "Length height for age"
        ],
        "summary": "Classify length/height-for-age (stunting), birth to 19 years",
        "description": "WHO Child Growth Standards length/height-for-age as a z-score. Length (lying) is used under 24 months, height (standing) from 24 months; if measured the other way, 0.7 cm is added or subtracted and reported in position_adjustment. Strict cut-offs: severely stunted below -3SD, stunted below -2SD, normal -2SD to +2SD, tall above +2SD, very tall above +3SD. implausible is true when |z| > 6. Screening aid only, not a diagnosis.\n",
        "parameters": [
          {
            "name": "sex",
            "in": "query",
            "required": true,
            "description": "girls or boys (girl, boy, female, male, f, m also accepted)",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "age_months",
            "in": "query",
            "required": true,
            "description": "Whole completed months",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "maximum": 228
            }
          },
          {
            "name": "length_cm",
            "in": "query",
            "description": "Length or height in CENTIMETRES (height_cm is an alias)",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "height_cm",
            "in": "query",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "length_mm",
            "in": "query",
            "description": "Millimetres (height_mm is an alias); used if no cm value is given",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "measured",
            "in": "query",
            "description": "How the child was measured; default lying under 24 months, standing from 24",
            "schema": {
              "type": "string",
              "enum": [
                "lying",
                "standing"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Classification",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Success"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "object",
                          "properties": {
                            "sex": {
                              "type": "string",
                              "enum": [
                                "girls",
                                "boys"
                              ]
                            },
                            "age_months": {
                              "type": "integer"
                            },
                            "measured": {
                              "type": "string",
                              "enum": [
                                "lying",
                                "standing"
                              ]
                            },
                            "entered_cm": {
                              "type": "number"
                            },
                            "position_adjustment": {
                              "type": "object",
                              "description": "Present only when 0.7 cm was added or subtracted"
                            },
                            "status": {
                              "type": "string",
                              "enum": [
                                "severely stunted",
                                "stunted",
                                "normal",
                                "tall",
                                "very tall"
                              ]
                            },
                            "reference": {
                              "type": "string",
                              "example": "length (lying down)"
                            },
                            "value_cm": {
                              "type": "number"
                            },
                            "z_score": {
                              "type": "number",
                              "nullable": true
                            },
                            "percentile": {
                              "type": "number",
                              "nullable": true
                            },
                            "implausible": {
                              "type": "boolean",
                              "nullable": true
                            },
                            "cutoffs": {
                              "type": "object",
                              "additionalProperties": {
                                "type": "number"
                              },
                              "description": "Keys -3SD, -2SD, -1SD, median, +1SD, +2SD, +3SD (cm)"
                            },
                            "source": {
                              "type": "string"
                            },
                            "note": {
                              "type": "string"
                            }
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/length-height-for-age": {
      "get": {
        "tags": [
          "Length height for age"
        ],
        "summary": "List WHO length/height-for-age rows (cm, with L/M/S)",
        "parameters": [
          {
            "name": "sex",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "age_months",
            "in": "query",
            "schema": {
              "type": "integer"
            }
          },
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Offset"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/ListOrCursor"
          }
        }
      }
    },
    "/length-height-for-age/bulk": {
      "post": {
        "tags": [
          "Length height for age"
        ],
        "summary": "Bulk-create length/height-for-age rows (admin)",
        "description": "Seed files scripts/length_height_for_age_seed.json (124 rows, 0-60 months) and scripts/height_for_age_5_19_seed.json (336 rows, 61-228 months; run sql/023 first). A unique (sex, age_months, measured) constraint rejects re-seeding.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BulkRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/BulkSuccess"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/weight-for-age/classify": {
      "get": {
        "tags": [
          "Weight for age"
        ],
        "summary": "Classify weight-for-age (underweight), birth to 10 years",
        "description": "WHO weight-for-age as a z-score (2006 standards to 60 months, 2007 reference 61-120 months). Strict cut-offs: severely underweight below -3SD, underweight below -2SD, normal -2SD to +2SD, high weight-for-age above +2SD, very high above +3SD. Not used to diagnose overweight; use weight-for-length/height or BMI-for-age. implausible is true when z < -6 or z > +5. Screening aid only, not a diagnosis.\n",
        "parameters": [
          {
            "name": "sex",
            "in": "query",
            "required": true,
            "description": "girls or boys (girl, boy, female, male, f, m also accepted)",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "age_months",
            "in": "query",
            "required": true,
            "description": "Whole completed months",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "maximum": 120
            }
          },
          {
            "name": "weight_kg",
            "in": "query",
            "description": "Weight in KILOGRAMS",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "weight_g",
            "in": "query",
            "description": "Weight in GRAMS (used if weight_kg is absent)",
            "schema": {
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Classification",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Success"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "object",
                          "properties": {
                            "sex": {
                              "type": "string",
                              "enum": [
                                "girls",
                                "boys"
                              ]
                            },
                            "age_months": {
                              "type": "integer"
                            },
                            "status": {
                              "type": "string",
                              "enum": [
                                "severely underweight",
                                "underweight",
                                "normal",
                                "high weight-for-age",
                                "very high weight-for-age"
                              ]
                            },
                            "weight_kg": {
                              "type": "number"
                            },
                            "z_score": {
                              "type": "number",
                              "nullable": true
                            },
                            "percentile": {
                              "type": "number",
                              "nullable": true
                            },
                            "implausible": {
                              "type": "boolean",
                              "nullable": true
                            },
                            "cutoffs": {
                              "type": "object",
                              "additionalProperties": {
                                "type": "number"
                              },
                              "description": "Keys -3SD, -2SD, -1SD, median, +1SD, +2SD, +3SD (kg)"
                            },
                            "source": {
                              "type": "string"
                            },
                            "note": {
                              "type": "string"
                            }
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/weight-for-age": {
      "get": {
        "tags": [
          "Weight for age"
        ],
        "summary": "List WHO weight-for-age rows (kg, with L/M/S)",
        "parameters": [
          {
            "name": "sex",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "age_months",
            "in": "query",
            "schema": {
              "type": "integer"
            }
          },
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Offset"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/ListOrCursor"
          }
        }
      }
    },
    "/weight-for-age/bulk": {
      "post": {
        "tags": [
          "Weight for age"
        ],
        "summary": "Bulk-create weight-for-age rows (admin)",
        "description": "Seed file scripts/weight_for_age_seed.json (242 rows). A unique (sex, age_months) constraint rejects re-seeding.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BulkRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/BulkSuccess"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/weight-for-length-height/classify": {
      "get": {
        "tags": [
          "Weight for length height"
        ],
        "summary": "Classify weight-for-length/height (wasting), birth to 5 years",
        "description": "WHO weight-for-length (under 24 months, lying, 45-110 cm) or weight-for-height (24-60 months, standing, 65-120 cm) as a z-score, looked up by length/height to 0.1 cm. If measured the other way for the age, 0.7 cm is added or subtracted and reported in position_adjustment. Strict cut-offs: severe wasting below -3SD, moderate wasting below -2SD, normal -2SD to +1SD, possible risk of overweight above +1SD, overweight above +2SD, obesity above +3SD. implausible is true when |z| > 5. Bilateral pitting oedema means severe acute malnutrition regardless of z-score. Screening aid only, not a diagnosis.\n",
        "parameters": [
          {
            "name": "sex",
            "in": "query",
            "required": true,
            "description": "girls or boys (girl, boy, female, male, f, m also accepted)",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "age_months",
            "in": "query",
            "required": true,
            "description": "Whole completed months",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "maximum": 60
            }
          },
          {
            "name": "weight_kg",
            "in": "query",
            "description": "Weight in KILOGRAMS",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "weight_g",
            "in": "query",
            "description": "Weight in GRAMS (used if weight_kg is absent)",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "length_cm",
            "in": "query",
            "description": "Length or height in CENTIMETRES (height_cm is an alias)",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "height_cm",
            "in": "query",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "length_mm",
            "in": "query",
            "description": "Millimetres (height_mm is an alias); used if no cm value is given",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "measured",
            "in": "query",
            "description": "How the child was measured; default lying under 24 months, standing from 24",
            "schema": {
              "type": "string",
              "enum": [
                "lying",
                "standing"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Classification",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Success"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "object",
                          "properties": {
                            "sex": {
                              "type": "string",
                              "enum": [
                                "girls",
                                "boys"
                              ]
                            },
                            "age_months": {
                              "type": "integer"
                            },
                            "measured": {
                              "type": "string",
                              "enum": [
                                "lying",
                                "standing"
                              ]
                            },
                            "entered_length_height_cm": {
                              "type": "number"
                            },
                            "lookup_length_height_cm": {
                              "type": "number"
                            },
                            "position_adjustment": {
                              "type": "object",
                              "description": "Present only when 0.7 cm was added or subtracted"
                            },
                            "status": {
                              "type": "string",
                              "enum": [
                                "severe wasting",
                                "moderate wasting",
                                "normal",
                                "possible risk of overweight",
                                "overweight",
                                "obesity"
                              ]
                            },
                            "reference": {
                              "type": "string",
                              "example": "weight-for-length (lying down)"
                            },
                            "weight_kg": {
                              "type": "number"
                            },
                            "z_score": {
                              "type": "number",
                              "nullable": true
                            },
                            "percentile": {
                              "type": "number",
                              "nullable": true
                            },
                            "implausible": {
                              "type": "boolean",
                              "nullable": true
                            },
                            "cutoffs": {
                              "type": "object",
                              "additionalProperties": {
                                "type": "number"
                              },
                              "description": "Keys -3SD, -2SD, -1SD, median, +1SD, +2SD, +3SD (kg)"
                            },
                            "source": {
                              "type": "string"
                            },
                            "note": {
                              "type": "string"
                            }
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "description": "Length/height is outside the WHO table for this age"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/weight-for-length-height": {
      "get": {
        "tags": [
          "Weight for length height"
        ],
        "summary": "List WHO weight-for-length/height rows (kg, with L/M/S)",
        "parameters": [
          {
            "name": "sex",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "measured",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "lying",
                "standing"
              ]
            }
          },
          {
            "name": "length_height_cm",
            "in": "query",
            "schema": {
              "type": "number"
            }
          },
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Offset"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/ListOrCursor"
          }
        }
      }
    },
    "/weight-for-length-height/bulk": {
      "post": {
        "tags": [
          "Weight for length height"
        ],
        "summary": "Bulk-create weight-for-length/height rows (admin)",
        "description": "Seed parts scripts/weight_for_length_height_seed_1.json to _5.json (2404 rows in total, at most 500 per request). A unique (sex, measured, length_height_cm) constraint rejects re-seeding.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BulkRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/BulkSuccess"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/bmi-for-age-under-5/classify": {
      "get": {
        "tags": [
          "BMI for age under 5"
        ],
        "summary": "Classify BMI-for-age, birth to 5 years",
        "description": "WHO Child Growth Standards BMI-for-age as a z-score (0-60 months). The age picks the table (lying under 24 months, standing from 24). Pass bmi, or weight with length/height (BMI is then computed after any 0.7 cm position adjustment, reported in position_adjustment). Strict cut-offs: severe wasting below -3SD, moderate wasting below -2SD, normal -2SD to +1SD, possible risk of overweight above +1SD, overweight above +2SD, obesity above +3SD. implausible is true when |z| > 5. 5-19 years use /bmi-for-age. Screening aid only, not a diagnosis.\n",
        "parameters": [
          {
            "name": "sex",
            "in": "query",
            "required": true,
            "description": "girls or boys (girl, boy, female, male, f, m also accepted)",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "age_months",
            "in": "query",
            "required": true,
            "description": "Whole completed months",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "maximum": 60
            }
          },
          {
            "name": "bmi",
            "in": "query",
            "description": "BMI in kg/m2 (alternative to weight plus length/height)",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "weight_kg",
            "in": "query",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "weight_g",
            "in": "query",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "length_cm",
            "in": "query",
            "description": "Length or height in CENTIMETRES (height_cm is an alias)",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "height_cm",
            "in": "query",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "length_mm",
            "in": "query",
            "description": "Millimetres (height_mm is an alias)",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "measured",
            "in": "query",
            "description": "How the child was measured; default lying under 24 months, standing from 24",
            "schema": {
              "type": "string",
              "enum": [
                "lying",
                "standing"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Classification",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Success"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "object",
                          "properties": {
                            "sex": {
                              "type": "string",
                              "enum": [
                                "girls",
                                "boys"
                              ]
                            },
                            "age_months": {
                              "type": "integer"
                            },
                            "bmi_computed_from_weight_and_length_height": {
                              "type": "boolean"
                            },
                            "position_adjustment": {
                              "type": "object",
                              "description": "Present only when 0.7 cm was added or subtracted"
                            },
                            "status": {
                              "type": "string",
                              "enum": [
                                "severe wasting",
                                "moderate wasting",
                                "normal",
                                "possible risk of overweight",
                                "overweight",
                                "obesity"
                              ]
                            },
                            "reference": {
                              "type": "string"
                            },
                            "bmi": {
                              "type": "number"
                            },
                            "z_score": {
                              "type": "number",
                              "nullable": true
                            },
                            "percentile": {
                              "type": "number",
                              "nullable": true
                            },
                            "implausible": {
                              "type": "boolean",
                              "nullable": true
                            },
                            "cutoffs": {
                              "type": "object",
                              "additionalProperties": {
                                "type": "number"
                              },
                              "description": "Keys -3SD, -2SD, -1SD, median, +1SD, +2SD, +3SD (kg/m2)"
                            },
                            "source": {
                              "type": "string"
                            },
                            "note": {
                              "type": "string"
                            }
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/bmi-for-age-under-5": {
      "get": {
        "tags": [
          "BMI for age under 5"
        ],
        "summary": "List WHO BMI-for-age rows, birth to 5 years (kg/m2, with L/M/S)",
        "parameters": [
          {
            "name": "sex",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "age_months",
            "in": "query",
            "schema": {
              "type": "integer"
            }
          },
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Offset"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/ListOrCursor"
          }
        }
      }
    },
    "/bmi-for-age-under-5/bulk": {
      "post": {
        "tags": [
          "BMI for age under 5"
        ],
        "summary": "Bulk-create BMI-for-age (under 5) rows (admin)",
        "description": "Seed file scripts/bmi_for_age_under5_seed.json (124 rows). A unique (sex, age_months, measured) constraint rejects re-seeding.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BulkRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/BulkSuccess"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/hc-for-age/classify": {
      "get": {
        "tags": [
          "HC for age"
        ],
        "summary": "Classify head circumference-for-age, birth to 5 years",
        "description": "WHO Child Growth Standards head circumference-for-age as a z-score (0-60 months). Strict cut-offs: severe microcephaly below -3SD, microcephaly below -2SD, normal -2SD to +2SD, macrocephaly above +2SD, severe macrocephaly above +3SD. implausible is true when |z| > 5. The 6-month increment is /hc-velocity/classify. Screening aid only, not a diagnosis.\n",
        "parameters": [
          {
            "name": "sex",
            "in": "query",
            "required": true,
            "description": "girls or boys (girl, boy, female, male, f, m also accepted)",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "age_months",
            "in": "query",
            "required": true,
            "description": "Whole completed months",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "maximum": 60
            }
          },
          {
            "name": "hc_cm",
            "in": "query",
            "description": "Head circumference in CENTIMETRES",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "hc_mm",
            "in": "query",
            "description": "Head circumference in MILLIMETRES (used if hc_cm is absent)",
            "schema": {
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Classification",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Success"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "object",
                          "properties": {
                            "sex": {
                              "type": "string",
                              "enum": [
                                "girls",
                                "boys"
                              ]
                            },
                            "age_months": {
                              "type": "integer"
                            },
                            "status": {
                              "type": "string",
                              "enum": [
                                "severe microcephaly",
                                "microcephaly",
                                "normal",
                                "macrocephaly",
                                "severe macrocephaly"
                              ]
                            },
                            "hc_cm": {
                              "type": "number"
                            },
                            "z_score": {
                              "type": "number",
                              "nullable": true
                            },
                            "percentile": {
                              "type": "number",
                              "nullable": true
                            },
                            "implausible": {
                              "type": "boolean",
                              "nullable": true
                            },
                            "cutoffs": {
                              "type": "object",
                              "additionalProperties": {
                                "type": "number"
                              },
                              "description": "Keys -3SD, -2SD, -1SD, median, +1SD, +2SD, +3SD (cm)"
                            },
                            "source": {
                              "type": "string"
                            },
                            "note": {
                              "type": "string"
                            }
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/hc-for-age": {
      "get": {
        "tags": [
          "HC for age"
        ],
        "summary": "List WHO head circumference-for-age rows (cm, with L/M/S)",
        "parameters": [
          {
            "name": "sex",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "age_months",
            "in": "query",
            "schema": {
              "type": "integer"
            }
          },
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Offset"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/ListOrCursor"
          }
        }
      }
    },
    "/hc-for-age/bulk": {
      "post": {
        "tags": [
          "HC for age"
        ],
        "summary": "Bulk-create head circumference-for-age rows (admin)",
        "description": "Seed file scripts/hc_for_age_seed.json (122 rows). A unique (sex, age_months) constraint rejects re-seeding.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BulkRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/BulkSuccess"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/energy/requirements": {
      "post": {
        "tags": [
          "Energy"
        ],
        "summary": "Energy requirement estimate (REE, stress, activity, IOM equations)",
        "description": "Estimates daily energy needs from the equations in Krause and Mahan's Food and the Nutrition Care Process (16th ed., chapter 2). Pure calculation, nothing is stored. Steps: (1) resting energy expenditure, by Mifflin-St Jeor for adults or a measured value you supply; (2) stress factors multiply REE (fever, or a clinician-set custom factor); (3) an activity factor multiplies the result (minimal 10-20%, moderate 25-40%, strenuous 45-60%, expert-opinion ranges); (4) every IOM EER or weight-maintenance TEE equation that fits the person is calculated from a PAL category, a PAL value or an activity log, with pregnancy and lactation add-ons. The reply lists every estimate with its steps, one recommended figure with the reason, warnings and notes. TEF is already inside the activity factors and IOM equations, so it is never added again. Mifflin-St Jeor is not predicted under 18. Harris-Benedict is not offered because the chapter does not recommend it. Estimates only: individual variation can be wide, and critically ill patients need measured REE or chapter 39 methods.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "sex"
                ],
                "properties": {
                  "sex": {
                    "type": "string",
                    "description": "male or female (m, f, man, woman, boy, girl also accepted)"
                  },
                  "age_years": {
                    "type": "number",
                    "description": "Age in years. Give this or age_months."
                  },
                  "age_months": {
                    "type": "integer",
                    "description": "Age in whole months (needed for infants under 3 years if age_years is not given)"
                  },
                  "weight_kg": {
                    "type": "number",
                    "description": "Weight in KILOGRAMS (0.5-500)"
                  },
                  "height_cm": {
                    "type": "number",
                    "description": "Height or length in CENTIMETRES (30-260). height_m also accepted. Needed for adults and for the IOM equations."
                  },
                  "measured_ree_kcal": {
                    "type": "number",
                    "description": "REE measured by indirect calorimetry. Replaces the predicted REE."
                  },
                  "rq": {
                    "description": "Respiratory quotient, as a number or {vo2, vco2}. Interpreted only.",
                    "oneOf": [
                      {
                        "type": "number"
                      },
                      {
                        "type": "object",
                        "properties": {
                          "vo2": {
                            "type": "number"
                          },
                          "vco2": {
                            "type": "number"
                          }
                        }
                      }
                    ]
                  },
                  "activity_factor": {
                    "type": "string",
                    "description": "A registered activity level from GET /energy/factors (minimal, moderate, strenuous)."
                  },
                  "activity_position": {
                    "description": "Where in the factor's range to sit - low, mid (default), high, or a number 0-1.",
                    "oneOf": [
                      {
                        "type": "string",
                        "enum": [
                          "low",
                          "mid",
                          "high"
                        ]
                      },
                      {
                        "type": "number"
                      }
                    ]
                  },
                  "pal": {
                    "type": "number",
                    "description": "Physical activity level, 1-2.5. Selects the PA coefficient for the IOM equations."
                  },
                  "pal_category": {
                    "type": "string",
                    "enum": [
                      "sedentary",
                      "low_active",
                      "active",
                      "very_active"
                    ]
                  },
                  "activities": {
                    "type": "array",
                    "description": "Daily activity log. PAL = 1 + 0.1 (TEF) + the sum of delta PAL. Each item has an id from the Table 2.4 list in GET /energy/factors, or its own delta_pal_per_hour, plus hours or minutes.",
                    "items": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string"
                        },
                        "label": {
                          "type": "string"
                        },
                        "delta_pal_per_hour": {
                          "type": "number"
                        },
                        "hours": {
                          "type": "number"
                        },
                        "minutes": {
                          "type": "number"
                        }
                      }
                    }
                  },
                  "activity_log": {
                    "type": "array",
                    "maxItems": 50,
                    "description": "Activities looked up in the database (Krause Appendix 10, see GET /activity-energy). Each item has activity, type (when the activity has more than one) and minutes or hours. kcal per hour at weight_kg is converted to METs (kcal per hour / kg), then to delta PAL per hour as (METs - 1) / 17.5, the relation Table 2.4 follows, and added to the PAL used by the IOM equations. It can be combined with activities. List your everyday activities as well (Table 2.4 ids in activities), otherwise the PAL is low. weight_kg must be 20-250 for a log. The reply adds an activity_log block with every step.",
                    "items": {
                      "type": "object",
                      "required": [
                        "activity"
                      ],
                      "properties": {
                        "activity": {
                          "type": "string"
                        },
                        "type": {
                          "type": "string"
                        },
                        "minutes": {
                          "type": "number"
                        },
                        "hours": {
                          "type": "number"
                        }
                      }
                    }
                  },
                  "bmi_for_age_percentile": {
                    "type": "number",
                    "description": "For children 3-18. Above 85 selects the overweight equation. Optional - when left out and the child has weight_kg and height_cm, the API looks it up in the WHO tables (exact from L, M, S for 36-60 months; interpolated between the -3 to +3 SD cut-offs for 5-19 years) and reports the source in inputs_used."
                  },
                  "stress": {
                    "type": "array",
                    "description": "Stress factors applied to REE. Known ids: fever (temperature_c or temperature_f) and custom (multiplier 0.5-3, label, source).",
                    "items": {
                      "type": "object",
                      "required": [
                        "id"
                      ],
                      "properties": {
                        "id": {
                          "type": "string"
                        },
                        "temperature_c": {
                          "type": "number"
                        },
                        "temperature_f": {
                          "type": "number"
                        },
                        "multiplier": {
                          "type": "number"
                        },
                        "label": {
                          "type": "string"
                        },
                        "source": {
                          "type": "string"
                        }
                      }
                    }
                  },
                  "pregnancy": {
                    "type": "object",
                    "properties": {
                      "trimester": {
                        "type": "integer",
                        "enum": [
                          1,
                          2,
                          3
                        ]
                      }
                    }
                  },
                  "lactation": {
                    "type": "object",
                    "properties": {
                      "months_postpartum": {
                        "type": "number"
                      }
                    }
                  },
                  "prefer": {
                    "type": "string",
                    "enum": [
                      "iom",
                      "ree"
                    ],
                    "description": "Force which estimate is recommended."
                  }
                }
              },
              "example": {
                "sex": "female",
                "age_years": 30,
                "weight_kg": 65,
                "height_cm": 177,
                "pal_category": "active",
                "activity_factor": "moderate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Estimate with every step",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Success"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "object",
                          "properties": {
                            "inputs_used": {
                              "type": "object"
                            },
                            "ree": {
                              "type": "object",
                              "nullable": true
                            },
                            "stress": {
                              "type": "object"
                            },
                            "activity": {
                              "type": "object"
                            },
                            "activity_log": {
                              "type": "object",
                              "nullable": true,
                              "description": "Present only when activity_log was sent. Each activity with kcal, METs and delta PAL, the method, total kcal and minutes, warnings and notes."
                            },
                            "estimates": {
                              "type": "array",
                              "items": {
                                "type": "object",
                                "description": "id, label, kcal, kcal_unrounded, basis, steps, source, evidence (IOM items also have pa_coefficient and may list alternatives)."
                              }
                            },
                            "recommended": {
                              "type": "object",
                              "nullable": true,
                              "description": "id, kcal and the reason it was chosen. Null when only REE could be calculated."
                            },
                            "components": {
                              "type": "object",
                              "nullable": true
                            },
                            "rq": {
                              "type": "object",
                              "nullable": true
                            },
                            "warnings": {
                              "type": "array",
                              "items": {
                                "type": "string"
                              }
                            },
                            "notes": {
                              "type": "array",
                              "items": {
                                "type": "string"
                              }
                            },
                            "sources": {
                              "type": "array",
                              "items": {
                                "type": "string"
                              }
                            },
                            "disclaimer": {
                              "type": "string"
                            }
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Missing or invalid input",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/energy/food-energy": {
      "post": {
        "tags": [
          "Energy"
        ],
        "summary": "Food energy from macronutrients or an alcoholic drink",
        "description": "Biologically available energy from the Atwater values in Krause and Mahan (16th ed., chapter 2) - protein 4, fat 9, carbohydrate 4 and alcohol 7 kcal per gram - with kJ at 4.184 per kcal. Send grams, or percent of the food's weight with weight_g (the chapter's egg example - 50 g, 13% protein, 12% fat, 1% carbohydrate - gives 82 kcal), or an alcoholic drink as volume in US fluid ounces with proof or abv_percent (kcal = oz x proof x 0.8; 1.5 oz of 86 proof is 103 kcal). Send available carbohydrate: fiber is an unavailable carbohydrate with minimal energy and is not added. Nothing is stored.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "protein_g": {
                    "type": "number"
                  },
                  "fat_g": {
                    "type": "number"
                  },
                  "carbohydrate_g": {
                    "type": "number",
                    "description": "Available carbohydrate in grams."
                  },
                  "alcohol_g": {
                    "type": "number"
                  },
                  "fiber_g": {
                    "type": "number",
                    "description": "Informational only. Not added to the energy."
                  },
                  "weight_g": {
                    "type": "number",
                    "description": "Food weight in grams. Needed with the percent fields, and gives a per-100 g figure with grams."
                  },
                  "protein_pct": {
                    "type": "number",
                    "description": "Percent of the food's weight (0-100), not percent of energy."
                  },
                  "fat_pct": {
                    "type": "number"
                  },
                  "carbohydrate_pct": {
                    "type": "number"
                  },
                  "alcohol_pct": {
                    "type": "number"
                  },
                  "alcohol": {
                    "type": "object",
                    "description": "An alcoholic drink. Use this or alcohol_g, not both.",
                    "properties": {
                      "volume_oz": {
                        "type": "number"
                      },
                      "proof": {
                        "type": "number"
                      },
                      "abv_percent": {
                        "type": "number"
                      }
                    }
                  }
                }
              },
              "example": {
                "weight_g": 50,
                "protein_pct": 13,
                "fat_pct": 12,
                "carbohydrate_pct": 1
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Energy with a per-nutrient breakdown",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Success"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "object",
                          "properties": {
                            "total": {
                              "type": "object",
                              "description": "kcal, kj and kcal_unrounded."
                            },
                            "breakdown": {
                              "type": "array",
                              "items": {
                                "type": "object",
                                "description": "nutrient, grams, kcal_per_gram, kcal, percent_of_energy and the working."
                              }
                            },
                            "per_100g": {
                              "type": "object",
                              "nullable": true
                            },
                            "alcohol_drink": {
                              "type": "object",
                              "nullable": true
                            },
                            "factors": {
                              "type": "object"
                            },
                            "warnings": {
                              "type": "array",
                              "items": {
                                "type": "string"
                              }
                            },
                            "notes": {
                              "type": "array",
                              "items": {
                                "type": "string"
                              }
                            },
                            "sources": {
                              "type": "array",
                              "items": {
                                "type": "string"
                              }
                            }
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Missing or invalid input",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/activity-energy": {
      "get": {
        "tags": [
          "Energy"
        ],
        "summary": "Physical activity energy table (kcal per hour by body weight)",
        "description": "Browse or search the activity table stored in the database (Krause and Mahan, 16th ed., Appendix 10: kcal per hour at 110 to 250 lb). Without a weight, each row returns its eight printed values and any printed value flagged as a likely misprint (suspect_weights_lb). With weight_kg or weight_lb, each row returns kcal per hour at that weight (printed value, interpolated, or scaled outside 110-250 lb) and how it was obtained.\n",
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Text to find in the activity or type, for example run or butterfly."
          },
          {
            "name": "activity",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Exact activity name, for example Swim."
          },
          {
            "name": "weight_kg",
            "in": "query",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "weight_lb",
            "in": "query",
            "schema": {
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Matching activities",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Success"
                }
              }
            }
          },
          "400": {
            "description": "Invalid weight",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/activity-energy/calculate": {
      "post": {
        "tags": [
          "Energy"
        ],
        "summary": "Energy spent on one or more activities",
        "description": "Send body weight and a list of activities with minutes or hours. Each activity is found in the database table by name (case-insensitive) and type; send type when an activity has more than one. kcal per hour is taken at the given weight (printed value, linear interpolation between printed weights, or proportional scaling outside 110-250 lb, which is flagged). Printed cells flagged as likely misprints are left out of the interpolation and the response says so. The source does not say whether resting energy is included. The table was built for adult weights. Nothing is stored.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "activities"
                ],
                "properties": {
                  "weight_kg": {
                    "type": "number",
                    "description": "Body weight in kg (20-250). Use this or weight_lb."
                  },
                  "weight_lb": {
                    "type": "number"
                  },
                  "activities": {
                    "type": "array",
                    "maxItems": 50,
                    "items": {
                      "type": "object",
                      "properties": {
                        "activity": {
                          "type": "string"
                        },
                        "type": {
                          "type": "string"
                        },
                        "minutes": {
                          "type": "number"
                        },
                        "hours": {
                          "type": "number",
                          "description": "Use minutes or hours, not both."
                        }
                      }
                    }
                  }
                }
              },
              "example": {
                "weight_kg": 68,
                "activities": [
                  {
                    "activity": "Run",
                    "type": "6 mph, 10 min/mile",
                    "minutes": 30
                  },
                  {
                    "activity": "Walk",
                    "type": "3 mph, 20 min/mile",
                    "hours": 1
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "kcal per activity and in total",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Success"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "object",
                          "properties": {
                            "weight": {
                              "type": "object"
                            },
                            "activities": {
                              "type": "array",
                              "items": {
                                "type": "object",
                                "description": "activity, type, minutes, kcal_per_hour, kcal, method and notes."
                              }
                            },
                            "total": {
                              "type": "object",
                              "description": "kcal, kj and minutes."
                            },
                            "warnings": {
                              "type": "array",
                              "items": {
                                "type": "string"
                              }
                            },
                            "notes": {
                              "type": "array",
                              "items": {
                                "type": "string"
                              }
                            },
                            "sources": {
                              "type": "array",
                              "items": {
                                "type": "string"
                              }
                            }
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Missing or invalid input, or unknown activity",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/activity-energy/bulk": {
      "post": {
        "tags": [
          "Energy"
        ],
        "summary": "Bulk-create activity energy rows (admin)",
        "description": "Seed file scripts/activity_energy_seed.json (130 rows). A unique (activity, type) constraint rejects re-seeding.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BulkRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/BulkSuccess"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/energy/factors": {
      "get": {
        "tags": [
          "Energy"
        ],
        "summary": "Energy engine factors and reference tables",
        "description": "Everything needed to build an input form for POST /energy/requirements - registered activity factors (with their ranges and evidence level), registered stress factors and their inputs, PAL categories (Table 2.3), the activity table (Table 2.4, METs and delta PAL), every IOM equation with its coefficients, infant energy deposition, pregnancy and lactation add-ons, the DRI reference EER table (Table 2.2), respiratory quotient reference values and the REE measurement protocol (Table 2.1). New factors registered in the engine appear here without any change to this route.",
        "responses": {
          "200": {
            "description": "Factor catalogue",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Success"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "object"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/growth/assess": {
      "get": {
        "tags": [
          "Growth assessment"
        ],
        "summary": "Combined WHO growth assessment (every applicable indicator in one call)",
        "description": "Runs every WHO indicator that applies to the child's age and the measurements supplied: length/height-for-age, weight-for-age, weight-for-length/height, BMI-for-age, head circumference-for-age, MUAC z-score and fixed MUAC cut-offs. Each result is identical to its own endpoint. Indicators that cannot be calculated are listed in not_assessed with the reason; a bad value in one measurement only errors that indicator. attention lists everything outside the normal range, most serious first. acute_malnutrition_signals combines weight-for-length/height and MUAC but cannot assess oedema. Screening aid only, not a diagnosis.\n",
        "parameters": [
          {
            "name": "sex",
            "in": "query",
            "required": true,
            "description": "girls or boys (girl, boy, female, male, f, m also accepted)",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "age_months",
            "in": "query",
            "required": true,
            "description": "Whole completed months (0-228)",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "maximum": 228
            }
          },
          {
            "name": "weight_kg",
            "in": "query",
            "description": "Weight in KILOGRAMS (weight_g in grams)",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "weight_g",
            "in": "query",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "length_cm",
            "in": "query",
            "description": "Length or height in CENTIMETRES (height_cm, length_mm, height_mm also accepted)",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "height_cm",
            "in": "query",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "hc_cm",
            "in": "query",
            "description": "Head circumference in CENTIMETRES (hc_mm in millimetres)",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "muac_cm",
            "in": "query",
            "description": "Mid-upper arm circumference in CENTIMETRES (muac_mm in millimetres)",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "measured",
            "in": "query",
            "description": "How length/height was measured; default lying under 24 months, standing from 24",
            "schema": {
              "type": "string",
              "enum": [
                "lying",
                "standing"
              ]
            }
          },
          {
            "name": "muac_scheme",
            "in": "query",
            "description": "Fixed MUAC scheme; cmam covers 6-59 months only. Default is cmam for 6-59 months (matches acute_malnutrition_signals), who_hiv_2008 otherwise.",
            "schema": {
              "type": "string",
              "enum": [
                "who_hiv_2008",
                "cmam"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Assessment",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Success"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "object",
                          "properties": {
                            "sex": {
                              "type": "string",
                              "enum": [
                                "girls",
                                "boys"
                              ]
                            },
                            "age_months": {
                              "type": "integer"
                            },
                            "indicators": {
                              "type": "object",
                              "description": "Keys length_height_for_age, weight_for_age, weight_for_length_height, bmi_for_age, hc_for_age, muac_for_age, muac_fixed (only those calculated). Each value has the same fields as its own endpoint (status, z_score, percentile, cutoffs, ...) or an error string.\n",
                              "additionalProperties": {
                                "type": "object"
                              }
                            },
                            "not_assessed": {
                              "type": "array",
                              "items": {
                                "type": "object",
                                "properties": {
                                  "indicator": {
                                    "type": "string"
                                  },
                                  "reason": {
                                    "type": "string"
                                  }
                                }
                              }
                            },
                            "attention": {
                              "type": "array",
                              "items": {
                                "type": "object",
                                "properties": {
                                  "indicator": {
                                    "type": "string"
                                  },
                                  "status": {
                                    "type": "string"
                                  },
                                  "severity": {
                                    "type": "string",
                                    "enum": [
                                      "severe",
                                      "concern",
                                      "watch"
                                    ]
                                  },
                                  "z_score": {
                                    "type": "number"
                                  }
                                }
                              }
                            },
                            "acute_malnutrition_signals": {
                              "type": "object",
                              "nullable": true,
                              "properties": {
                                "level": {
                                  "type": "string",
                                  "enum": [
                                    "none",
                                    "moderate",
                                    "severe"
                                  ]
                                },
                                "reasons": {
                                  "type": "array",
                                  "items": {
                                    "type": "string"
                                  }
                                },
                                "oedema_assessed": {
                                  "type": "boolean",
                                  "example": false
                                },
                                "note": {
                                  "type": "string"
                                }
                              }
                            },
                            "note": {
                              "type": "string"
                            }
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/fenton-preterm/classify": {
      "get": {
        "tags": [
          "Fenton Preterm"
        ],
        "summary": "Classify a preterm infant measurement (weight, length, or head circumference)",
        "description": "Fenton preterm growth chart z-score/percentile/status, 2013 or 2025 reference. Licensed from Dr. Tanis Fenton (CC BY-NC-ND 4.0, non-commercial, this app only) — raw L/M/S values are never returned. There is no list/dump route for this resource. Screening aid only.\n",
        "parameters": [
          {
            "name": "sex",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "boys",
                "girls"
              ]
            }
          },
          {
            "name": "metric",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "weight",
                "length",
                "hc"
              ]
            }
          },
          {
            "name": "gest_age_weeks",
            "in": "query",
            "required": true,
            "schema": {
              "type": "number",
              "minimum": 22,
              "maximum": 50
            }
          },
          {
            "name": "day",
            "in": "query",
            "description": "Day of the gestational week (0-6). Default 0.",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "maximum": 6
            }
          },
          {
            "name": "value",
            "in": "query",
            "required": true,
            "description": "Grams for weight, cm for length/hc.",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "reference_year",
            "in": "query",
            "description": "Default 2025.",
            "schema": {
              "type": "integer",
              "enum": [
                2013,
                2025
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Classification",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Success"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "object",
                          "properties": {
                            "sex": {
                              "type": "string"
                            },
                            "metric": {
                              "type": "string"
                            },
                            "reference_year": {
                              "type": "integer"
                            },
                            "gest_age_weeks": {
                              "type": "number"
                            },
                            "day": {
                              "type": "integer"
                            },
                            "value": {
                              "type": "number"
                            },
                            "z": {
                              "type": "number"
                            },
                            "percentile": {
                              "type": "number"
                            },
                            "status": {
                              "type": "string",
                              "enum": [
                                "small",
                                "appropriate",
                                "large"
                              ]
                            },
                            "note": {
                              "type": "string"
                            }
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/fenton-preterm/profile": {
      "post": {
        "tags": [
          "Fenton Preterm"
        ],
        "summary": "Classify weight, length, and head circumference together at one timepoint",
        "description": "At least one of weight/length/hc required. Never returns L/M/S.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "sex",
                  "gest_age_weeks"
                ],
                "properties": {
                  "sex": {
                    "type": "string",
                    "enum": [
                      "boys",
                      "girls"
                    ]
                  },
                  "reference_year": {
                    "type": "integer",
                    "enum": [
                      2013,
                      2025
                    ]
                  },
                  "gest_age_weeks": {
                    "type": "number",
                    "minimum": 22,
                    "maximum": 50
                  },
                  "day": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 6
                  },
                  "weight": {
                    "type": "number",
                    "description": "grams"
                  },
                  "length": {
                    "type": "number",
                    "description": "cm"
                  },
                  "hc": {
                    "type": "number",
                    "description": "cm"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Profile classification",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Success"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/fenton-preterm/growth": {
      "post": {
        "tags": [
          "Fenton Preterm"
        ],
        "summary": "Classify a series of same-metric measurements over time (trajectory + trend)",
        "description": "Up to 50 points. Response is chart-ready (age/value/z/percentile per point) plus a descriptive trend block. Never returns L/M/S.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "sex",
                  "metric",
                  "measurements"
                ],
                "properties": {
                  "sex": {
                    "type": "string",
                    "enum": [
                      "boys",
                      "girls"
                    ]
                  },
                  "metric": {
                    "type": "string",
                    "enum": [
                      "weight",
                      "length",
                      "hc"
                    ]
                  },
                  "reference_year": {
                    "type": "integer",
                    "enum": [
                      2013,
                      2025
                    ]
                  },
                  "measurements": {
                    "type": "array",
                    "maxItems": 50,
                    "items": {
                      "type": "object",
                      "required": [
                        "gest_age_weeks",
                        "value"
                      ],
                      "properties": {
                        "gest_age_weeks": {
                          "type": "number"
                        },
                        "day": {
                          "type": "integer",
                          "minimum": 0,
                          "maximum": 6
                        },
                        "value": {
                          "type": "number"
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Trajectory + trend",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Success"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/fenton-preterm/velocity": {
      "post": {
        "tags": [
          "Fenton Preterm"
        ],
        "summary": "Growth velocity between consecutive same-metric measurements",
        "description": "Same body shape as /growth, 2+ points required. Weight in g/kg/day (average-weight method); length/hc in cm/week.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "sex",
                  "metric",
                  "measurements"
                ],
                "properties": {
                  "sex": {
                    "type": "string",
                    "enum": [
                      "boys",
                      "girls"
                    ]
                  },
                  "metric": {
                    "type": "string",
                    "enum": [
                      "weight",
                      "length",
                      "hc"
                    ]
                  },
                  "reference_year": {
                    "type": "integer",
                    "enum": [
                      2013,
                      2025
                    ]
                  },
                  "measurements": {
                    "type": "array",
                    "minItems": 2,
                    "maxItems": 50,
                    "items": {
                      "type": "object",
                      "required": [
                        "gest_age_weeks",
                        "value"
                      ],
                      "properties": {
                        "gest_age_weeks": {
                          "type": "number"
                        },
                        "day": {
                          "type": "integer",
                          "minimum": 0,
                          "maximum": 6
                        },
                        "value": {
                          "type": "number"
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Velocity per consecutive interval",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Success"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/fenton-preterm/chart": {
      "get": {
        "tags": [
          "Fenton Preterm"
        ],
        "summary": "Server-rendered SVG growth chart (P3/P10/P50/P90/P97 curves)",
        "description": "Returns a rendered SVG image, never numeric curve data — this is the one endpoint that touches the full reference curve, and it stays an image by design (mirrors Dr. Fenton's own public tool, which outputs jpg/pdf, not data points).\n",
        "parameters": [
          {
            "name": "sex",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "boys",
                "girls"
              ]
            }
          },
          {
            "name": "metric",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "weight",
                "length",
                "hc"
              ]
            }
          },
          {
            "name": "reference_year",
            "in": "query",
            "schema": {
              "type": "integer",
              "enum": [
                2013,
                2025
              ]
            }
          },
          {
            "name": "points",
            "in": "query",
            "description": "Optional overlay, e.g. \"28w0d:900,30w0d:1200,32w0d:1500\" (day suffix optional).",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "SVG image",
            "content": {
              "image/svg+xml": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/fenton-preterm/references": {
      "get": {
        "tags": [
          "Fenton Preterm"
        ],
        "summary": "Static reference metadata (years, sexes, metrics, valid age ranges, citations, license)",
        "description": "No L/M/S values or anything the curve could be reconstructed from.",
        "responses": {
          "200": {
            "description": "Metadata",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Success"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/fenton-preterm/bulk": {
      "post": {
        "tags": [
          "Fenton Preterm"
        ],
        "summary": "Bulk-create Fenton preterm reference rows (admin)",
        "description": "Seed files scripts/fenton_preterm_seed_1.json .. _5.json (2284 rows total). A unique (reference_year, sex, metric, time_days) constraint rejects re-seeding.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BulkRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/BulkSuccess"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/fhir/metadata": {
      "get": {
        "tags": [
          "FHIR"
        ],
        "summary": "FHIR R4 CapabilityStatement",
        "description": "Stateless FHIR R4 facade, no storage, not yet registered with any OpenHIM/HIE instance. Lists every operation the facade exposes: `$evaluate-growth`, the Nutrition Care Process operations (`$screen`, `$assess-nutrition`, `$diagnose`, `$intervene`, `$monitor`, `Goal/$evaluate`, `$ncp-document`, `$care-context`, `$nutrition-assessment`), validation (`$validate`, `$validate-document`, `QuestionnaireResponse/$validate`) and `ConceptMap/$translate`. The statement declares that every operation except the conformance reads needs an API key. This read needs no key.",
        "security": [],
        "responses": {
          "200": {
            "description": "CapabilityStatement",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "resourceType": {
                      "type": "string",
                      "enum": [
                        "CapabilityStatement"
                      ]
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "draft"
                      ]
                    },
                    "date": {
                      "type": "string",
                      "format": "date"
                    },
                    "kind": {
                      "type": "string",
                      "enum": [
                        "instance"
                      ]
                    },
                    "software": {
                      "type": "object",
                      "properties": {
                        "name": {
                          "type": "string",
                          "example": "Chakudya Nutrition Registry (CNR) FHIR facade"
                        },
                        "version": {
                          "type": "string",
                          "example": "phase-1"
                        }
                      }
                    },
                    "implementation": {
                      "type": "object",
                      "properties": {
                        "description": {
                          "type": "string"
                        },
                        "url": {
                          "type": "string",
                          "format": "uri"
                        }
                      }
                    },
                    "fhirVersion": {
                      "type": "string",
                      "example": "4.0.1"
                    },
                    "format": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      },
                      "example": [
                        "json"
                      ]
                    },
                    "rest": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "mode": {
                            "type": "string",
                            "enum": [
                              "server"
                            ]
                          },
                          "resource": {
                            "type": "array",
                            "items": {
                              "type": "object",
                              "properties": {
                                "type": {
                                  "type": "string",
                                  "example": "Observation"
                                },
                                "operation": {
                                  "type": "array",
                                  "items": {
                                    "type": "object",
                                    "properties": {
                                      "name": {
                                        "type": "string",
                                        "example": "evaluate-growth"
                                      },
                                      "definition": {
                                        "type": "string",
                                        "format": "uri"
                                      },
                                      "documentation": {
                                        "type": "string"
                                      }
                                    }
                                  }
                                }
                              }
                            }
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/fhir/Observation/$evaluate-growth": {
      "post": {
        "tags": [
          "FHIR"
        ],
        "summary": "Evaluate a growth/anthropometry measurement, FHIR-shaped",
        "description": "Routes on age: gestationalAgeWeeks (22-50) -> Fenton preterm; birthDate resolving to 0-19 years postnatal -> every WHO indicator the measurements allow (length/height-for-age, weight-for-age, weight-for-length/height, BMI-for-age, head circumference-for-age, MUAC-for-age, fixed MUAC cut-offs, acute-malnutrition signals), the same calculations as /growth/assess, returned as a Bundle of Observations with one Observation per indicator. Measurement types: weight (kg|g), length (cm|m), hc (cm|m), muac (cm|mm), bmi. Optional parameters measured (lying|standing) and muacScheme (who_hiv_2008|cmam). Fenton with a single measurement returns a single Observation. Access: this operation needs a Bearer credential carrying the scope `system/Observation.write`. A missing credential returns 401, a missing scope 403 (both as OperationOutcome).",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "resourceType",
                  "parameter"
                ],
                "description": "A FHIR Parameters resource.",
                "properties": {
                  "resourceType": {
                    "type": "string",
                    "enum": [
                      "Parameters"
                    ]
                  },
                  "parameter": {
                    "type": "array",
                    "items": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Observation or Bundle of Observations",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "FHIR OperationOutcome — malformed input",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FhirOperationOutcome"
                }
              }
            }
          },
          "422": {
            "description": "FHIR OperationOutcome — valid input, unsupported age/route (e.g. older than 19 years)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FhirOperationOutcome"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "401": {
            "description": "Missing or invalid credential",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FhirOperationOutcome"
                }
              }
            }
          },
          "403": {
            "description": "Credential lacks the required FHIR scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FhirOperationOutcome"
                }
              }
            }
          }
        }
      }
    },
    "/fhir/Observation/$screen": {
      "post": {
        "tags": [
          "FHIR"
        ],
        "summary": "NCP screening (MUST, NRS-2002, MNA, SGA, PG-SGA or NACS classification)",
        "description": "tool=must (default) computes the Malnutrition Universal Screening Tool score and risk category; tool=nrs-2002 runs the two-stage Nutritional Risk Screening 2002 (initial screening, then nutritional status + disease severity + age 70+; total 3 or more = at risk). Disease severity and impaired general condition are clinician inputs. tool=mna runs the full Mini Nutritional Assessment: screening items A-F always; assessment items G-R only when the screening score is 11 or less (12-14 stops at normal). Total max 30: 24-30 normal, 17-23.5 at risk, below 17 malnourished. tool=sga records a Subjective Global Assessment: history descriptors are mapped to their printed A/B/C columns, % weight loss is derived, physical-examination grades are checked against the form, and the overall A/B/C rating is the assessor's own required input (the form gives no rule to compute it). tool=pg-sga scores the PG-SGA as printed (total = boxes 1-4 + disease + metabolic demand + physical exam, triaged 0-1, 2-3, 4-8, 9 or more); the physical-exam score is the clinician's overall deficit rating and the Stage A/B/C category is an optional assessor input. tool=nacs applies the NACS User's Guide Module 2 classification (children and adults): MUAC by age group and pregnancy status, adult BMI, BMI-for-age z-score, pre-pregnancy BMI, and bilateral pitting edema (severe acute malnutrition on its own). Each indicator is reported separately because the guide gives no combining rule. Returns a Bundle with one screening Observation. Stateless: nothing is stored. Body is a FHIR Parameters resource; success is a Bundle; errors are OperationOutcomes. See the API README, \"NCP operations\". Access: this operation needs a Bearer credential carrying the scope `system/Observation.write`. A missing credential returns 401, a missing scope 403 (both as OperationOutcome).",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "resourceType",
                  "parameter"
                ],
                "properties": {
                  "resourceType": {
                    "type": "string",
                    "enum": [
                      "Parameters"
                    ]
                  },
                  "parameter": {
                    "type": "array",
                    "items": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "FHIR Bundle",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "FHIR OperationOutcome — malformed or incomplete input",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "422": {
            "description": "FHIR OperationOutcome — valid input, unsupported case (e.g. a child sent to the adult assessment)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "401": {
            "description": "Missing or invalid credential",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FhirOperationOutcome"
                }
              }
            }
          },
          "403": {
            "description": "Credential lacks the required FHIR scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FhirOperationOutcome"
                }
              }
            }
          }
        }
      }
    },
    "/fhir/Observation/$assess-nutrition": {
      "post": {
        "tags": [
          "FHIR"
        ],
        "summary": "NCP Step 1 — nutrition assessment (adults)",
        "description": "BMI and WHO class, % weight change with standard-interval significance, Mifflin-St Jeor BEE and clinician-factored energy/protein requirements, intake adequacy, GLIM criteria, and generic FH/AD/BD/PD/CH indicators, as Observations. Children are redirected (422) to $evaluate-growth. Stateless: nothing is stored. Body is a FHIR Parameters resource; success is a Bundle; errors are OperationOutcomes. See the API README, \"NCP operations\". Access: this operation needs a Bearer credential carrying the scope `system/Observation.write`. A missing credential returns 401, a missing scope 403 (both as OperationOutcome).",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "resourceType",
                  "parameter"
                ],
                "properties": {
                  "resourceType": {
                    "type": "string",
                    "enum": [
                      "Parameters"
                    ]
                  },
                  "parameter": {
                    "type": "array",
                    "items": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "FHIR Bundle",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "FHIR OperationOutcome — malformed or incomplete input",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "422": {
            "description": "FHIR OperationOutcome — valid input, unsupported case (e.g. a child sent to the adult assessment)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "401": {
            "description": "Missing or invalid credential",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FhirOperationOutcome"
                }
              }
            }
          },
          "403": {
            "description": "Credential lacks the required FHIR scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FhirOperationOutcome"
                }
              }
            }
          }
        }
      }
    },
    "/fhir/Condition/$diagnose": {
      "post": {
        "tags": [
          "FHIR"
        ],
        "summary": "NCP Step 2 — nutrition diagnosis (PES statement)",
        "description": "Validates and shapes clinician-supplied PES statements (problem, etiology, signs/symptoms) into Condition resources. Does not choose the diagnosis. problemCode values like NI-5.2 are resolved against the NCP diagnostic terminology in Supabase (503 if the terminology is not loaded). The PES statement is stored as components (`pes-statement` extension: statement, coded problem, etiologies, signs with evidence references) as well as text. Optional `etiologyItem` / `signItem` parts add coded components and evidence: a `signItem` can hold an inline `observation` (validated like $nutrition-assessment, returned in the Bundle and linked from Condition.evidence) or `evidence` references to existing Observations; `additionalCoding` adds ICD-10/SNOMED CT beside the NCP term. With `author` (and optional `source`, `asserter`, `encounter`) the Conditions get recorder/encounter and one Provenance per diagnosis. Stateless: nothing is stored. Body is a FHIR Parameters resource; success is a Bundle of Conditions (plus evidence Observations and Provenance); errors are OperationOutcomes listing every problem. See the API README, \"NCP operations\". Access: this operation needs a Bearer credential carrying the scope `system/Condition.write`. A missing credential returns 401, a missing scope 403 (both as OperationOutcome).",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "resourceType",
                  "parameter"
                ],
                "properties": {
                  "resourceType": {
                    "type": "string",
                    "enum": [
                      "Parameters"
                    ]
                  },
                  "parameter": {
                    "type": "array",
                    "items": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "FHIR Bundle",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "FHIR OperationOutcome — malformed or incomplete input",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "422": {
            "description": "FHIR OperationOutcome — valid input, unsupported case (e.g. a child sent to the adult assessment)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "401": {
            "description": "Missing or invalid credential",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FhirOperationOutcome"
                }
              }
            }
          },
          "403": {
            "description": "Credential lacks the required FHIR scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FhirOperationOutcome"
                }
              }
            }
          }
        }
      }
    },
    "/fhir/CarePlan/$intervene": {
      "post": {
        "tags": [
          "FHIR"
        ],
        "summary": "NCP Step 3 — nutrition intervention",
        "description": "Shapes clinician-supplied goals, NutritionOrders (oral diet, supplement, enteral formula), education, counseling, care coordination, follow-up and referrals into a CarePlan Bundle (CarePlan, Goal, NutritionOrder, ServiceRequest, Task). Defaults to status draft / intent proposal. Pass the $diagnose Bundle as `resource` to link the plan, goals, orders, requests and tasks to the nutrition diagnosis. Goal dates, measures and units, order units and diagnosis references are validated and every problem is reported together (400). Activities with `assignedTo` or `dueDate`, and every `followUp` and `referral`, also create a Task. With `author` the Bundle includes a Provenance. Stateless: nothing is stored. Body is a FHIR Parameters resource; success is a Bundle; errors are OperationOutcomes. See the API README, \"NCP operations\". Access: this operation needs a Bearer credential carrying the scope `system/CarePlan.write`. A missing credential returns 401, a missing scope 403 (both as OperationOutcome).",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "resourceType",
                  "parameter"
                ],
                "properties": {
                  "resourceType": {
                    "type": "string",
                    "enum": [
                      "Parameters"
                    ]
                  },
                  "parameter": {
                    "type": "array",
                    "items": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "FHIR Bundle",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "FHIR OperationOutcome — malformed or incomplete input",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "422": {
            "description": "FHIR OperationOutcome — valid input, unsupported case (e.g. a child sent to the adult assessment)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "401": {
            "description": "Missing or invalid credential",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FhirOperationOutcome"
                }
              }
            }
          },
          "403": {
            "description": "Credential lacks the required FHIR scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FhirOperationOutcome"
                }
              }
            }
          }
        }
      }
    },
    "/fhir/CarePlan/$monitor": {
      "post": {
        "tags": [
          "FHIR"
        ],
        "summary": "NCP Step 4 - monitoring and evaluation of a follow-up visit",
        "description": "Records a follow-up against a care plan. Follow-up `observation` and `questionnaireResponse` parts are validated like $nutrition-assessment and returned as Observations/QuestionnaireResponses linked to the patient, the encounter (required) and the plan, with change from baseline and from the previous value. Each Goal of the plan (pass the $intervene Bundle as `resource`) is evaluated against the follow-up value of its measure or an explicit `current`, and returned with achievementStatus, an outcome reference, progress and the patient's response. When a goal is worsening, unchanged, not achieved or overdue, a plan-review Task is created and a draft successor CarePlan carries the plan forward; the original plan is never edited. With `author` the Bundle includes a Provenance. Units are never converted. Stateless: nothing is stored. Body is a FHIR Parameters resource; success is a Bundle; errors are OperationOutcomes. See the API README, \"NCP operations\". Access: this operation needs a Bearer credential carrying the scope `system/CarePlan.write`. A missing credential returns 401, a missing scope 403 (both as OperationOutcome).",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "resourceType",
                  "parameter"
                ],
                "properties": {
                  "resourceType": {
                    "type": "string",
                    "enum": [
                      "Parameters"
                    ]
                  },
                  "parameter": {
                    "type": "array",
                    "items": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "FHIR Bundle",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "FHIR OperationOutcome - malformed or incomplete input (every problem listed)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "422": {
            "description": "FHIR OperationOutcome - nothing could be recorded or evaluated",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "401": {
            "description": "Missing or invalid credential",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FhirOperationOutcome"
                }
              }
            }
          },
          "403": {
            "description": "Credential lacks the required FHIR scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FhirOperationOutcome"
                }
              }
            }
          }
        }
      }
    },
    "/fhir/Goal/$evaluate": {
      "post": {
        "tags": [
          "FHIR"
        ],
        "summary": "NCP Step 4 — monitoring and evaluation",
        "description": "Compares current values to baseline/target and returns Goals with achievementStatus plus outcome Observations. Direction of change is not guessed without a baseline; units must match. Stateless: nothing is stored. Body is a FHIR Parameters resource; success is a Bundle; errors are OperationOutcomes. See the API README, \"NCP operations\". Access: this operation needs a Bearer credential carrying the scope `system/Goal.write`. A missing credential returns 401, a missing scope 403 (both as OperationOutcome).",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "resourceType",
                  "parameter"
                ],
                "properties": {
                  "resourceType": {
                    "type": "string",
                    "enum": [
                      "Parameters"
                    ]
                  },
                  "parameter": {
                    "type": "array",
                    "items": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "FHIR Bundle",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "FHIR OperationOutcome — malformed or incomplete input",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "422": {
            "description": "FHIR OperationOutcome — valid input, unsupported case (e.g. a child sent to the adult assessment)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "401": {
            "description": "Missing or invalid credential",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FhirOperationOutcome"
                }
              }
            }
          },
          "403": {
            "description": "Credential lacks the required FHIR scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FhirOperationOutcome"
                }
              }
            }
          }
        }
      }
    },
    "/fhir/Bundle/$ncp-document": {
      "post": {
        "tags": [
          "FHIR"
        ],
        "summary": "NCP Step 5 - complete ADIME document Bundle",
        "description": "Assembles resources/Bundles returned by the other NCP operations (send the $intervene Bundle before the $monitor Bundle) into one ADIME Composition document Bundle (assessment, diagnosis, intervention, monitoring and evaluation sections; screening when present) with identifiers, a narrative and optional `status` (preliminary, final, amended), `custodian` and `confidentiality` (U, L, M, N default, R, V). The document declares profiles, carries a confidentiality label and a Provenance, and must pass the R4 structural check. Practitioner, PractitionerRole, Organization and CareTeam resources (from $care-context) are carried as supporting entries and wired in (author, custodian, CarePlan.careTeam). The document is validated before it is returned: structure, section placement, internal references, one patient. Any problem is a 422 listing every error. Patient, Encounter and QuestionnaireResponse entries are carried as supporting entries; `subject` and `encounter` default to the Patient and Encounter entries of a supplied $nutrition-assessment Bundle. Stateless: nothing is stored. Body is a FHIR Parameters resource; success is a Bundle; errors are OperationOutcomes. See the API README, \"NCP operations\". Access: this operation needs a Bearer credential carrying the scope `system/Bundle.write`. A missing credential returns 401, a missing scope 403 (both as OperationOutcome).",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "resourceType",
                  "parameter"
                ],
                "properties": {
                  "resourceType": {
                    "type": "string",
                    "enum": [
                      "Parameters"
                    ]
                  },
                  "parameter": {
                    "type": "array",
                    "items": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "FHIR Bundle",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "FHIR OperationOutcome — malformed or incomplete input",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "422": {
            "description": "FHIR OperationOutcome - the document failed validation (broken reference, wrong section, mixed patients; every error listed)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "401": {
            "description": "Missing or invalid credential",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FhirOperationOutcome"
                }
              }
            }
          },
          "403": {
            "description": "Credential lacks the required FHIR scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FhirOperationOutcome"
                }
              }
            }
          }
        }
      }
    },
    "/fhir/Bundle/$care-context": {
      "post": {
        "tags": [
          "FHIR"
        ],
        "summary": "NCP Step 6 - professional and institutional context",
        "description": "Shapes the clinicians and the facility behind a nutrition note into linked Organization, Practitioner, PractitionerRole and CareTeam resources. Repeatable `organization`, `practitioner`, `role` and `careTeam` parts refer to each other by a local `id` (or by a reference to a resource the caller already holds). A CareTeam needs `subject` (the patient) and members; roles outside the built-in list are kept as text. Registration numbers are recorded, never verified. Pass the Bundle to $ncp-document as a `resource`: the author is resolved to the PractitionerRole, the facility becomes Composition.custodian and the CareTeam is linked from the CarePlan. Stateless: nothing is stored. Body is a FHIR Parameters resource; success is a collection Bundle; errors are OperationOutcomes. See the API README, \"NCP operations\". Access: this operation needs a Bearer credential carrying the scope `system/Bundle.write`. A missing credential returns 401, a missing scope 403 (both as OperationOutcome).",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "resourceType",
                  "parameter"
                ],
                "properties": {
                  "resourceType": {
                    "type": "string",
                    "enum": [
                      "Parameters"
                    ]
                  },
                  "parameter": {
                    "type": "array",
                    "items": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "FHIR Bundle (collection) of Organization, Practitioner, PractitionerRole and CareTeam",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "FHIR OperationOutcome - malformed or incomplete input (every problem listed)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "401": {
            "description": "Missing or invalid credential",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FhirOperationOutcome"
                }
              }
            }
          },
          "403": {
            "description": "Credential lacks the required FHIR scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FhirOperationOutcome"
                }
              }
            }
          }
        }
      }
    },
    "/fhir/$validate": {
      "post": {
        "tags": [
          "FHIR"
        ],
        "summary": "Structural R4 check of one resource or Bundle",
        "description": "System-level $validate. Checks element names, required elements, cardinality, required code bindings, primitive formats, references, extensions, narrative and the main invariants for the resource types this facade emits, plus the minimum cardinality of any Chakudya profile in meta.profile. NOT the HL7 FHIR Validator and not an implementation-guide check. Always 200 with an OperationOutcome. Access: this operation needs a Bearer credential carrying the scope `system/Resource.read`. A missing credential returns 401, a missing scope 403 (both as OperationOutcome).",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "resourceType",
                  "parameter"
                ],
                "properties": {
                  "resourceType": {
                    "type": "string",
                    "enum": [
                      "Parameters"
                    ]
                  },
                  "parameter": {
                    "type": "array",
                    "items": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "FHIR OperationOutcome (errors, warnings, or one information issue)",
            "content": {
              "application/fhir+json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "FHIR OperationOutcome - body is not Parameters or has no single resource"
          },
          "401": {
            "description": "Missing or invalid credential",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FhirOperationOutcome"
                }
              }
            }
          },
          "403": {
            "description": "Credential lacks the required FHIR scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FhirOperationOutcome"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/fhir/ConceptMap/$translate": {
      "post": {
        "tags": [
          "FHIR"
        ],
        "summary": "Translate a Chakudya code (LOINC, HL7 practitioner-role, v3 ObservationInterpretation)",
        "description": "Parameters `system`, `code`, optional `url` (ConceptMap id) and `targetSystem`. Returns Parameters with `result` and one `match` per mapping. A code with no true mapping gives result=false; nothing is guessed. It is an operation, not a conformance read, so it needs an API key (scope `system/ConceptMap.read` or a wildcard).\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "resourceType",
                  "parameter"
                ],
                "properties": {
                  "resourceType": {
                    "type": "string",
                    "enum": [
                      "Parameters"
                    ]
                  },
                  "parameter": {
                    "type": "array",
                    "items": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "FHIR Parameters (result, message, match)",
            "content": {
              "application/fhir+json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "FHIR OperationOutcome - code and system are required"
          },
          "404": {
            "description": "FHIR OperationOutcome - unknown ConceptMap"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/fhir/ConceptMap/{id}": {
      "get": {
        "tags": [
          "FHIR"
        ],
        "summary": "Published terminology mapping",
        "description": "assessment-item-to-loinc, growth-metric-to-loinc, ncp-care-role-to-practitioner-role, growth-status-to-interpretation.",
        "security": [],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "FHIR ConceptMap",
            "content": {
              "application/fhir+json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "description": "FHIR OperationOutcome"
          }
        }
      }
    },
    "/fhir/AuditEvent": {
      "get": {
        "tags": [
          "FHIR"
        ],
        "summary": "Search stored audit events (admin)",
        "description": "Requires the root key, the admin role, or a credential with an explicit system/AuditEvent.read scope, . Read-only and immutable through the API. Answers 501 when audit storage is off. Events never contain request bodies, query strings, tokens, full IPs or (by default) patient references. Always needs a credential: the admin role (or root key) or an explicit `system/AuditEvent.read` scope. A wildcard `system/*` scope never covers AuditEvent.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "date",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "ge/le/gt/lt/eq prefix on recorded, e.g. ge2026-10-01; may be given twice for a range"
          },
          {
            "name": "outcome",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "0",
                "4",
                "8",
                "12"
              ]
            }
          },
          {
            "name": "agent-name",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Label of the API key"
          },
          {
            "name": "operation",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Filter by the audited route or operation name"
          },
          {
            "name": "_count",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 50,
              "maximum": 200
            }
          },
          {
            "name": "_offset",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 0
            },
            "description": "Number of events to skip, for paging. The Bundle's total is the full match count."
          }
        ],
        "responses": {
          "200": {
            "description": "FHIR searchset Bundle of AuditEvent",
            "content": {
              "application/fhir+json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "FHIR OperationOutcome - bad date or outcome"
          },
          "401": {
            "description": "Credential required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FhirOperationOutcome"
                }
              }
            }
          },
          "403": {
            "description": "Credential lacks admin role or system/AuditEvent.read",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FhirOperationOutcome"
                }
              }
            }
          },
          "501": {
            "description": "FHIR OperationOutcome (not-supported) - audit storage not enabled"
          }
        }
      }
    },
    "/fhir/AuditEvent/{id}": {
      "get": {
        "tags": [
          "FHIR"
        ],
        "summary": "Read one stored audit event (admin)",
        "description": "The id is the request id (X-Request-Id) of the audited request. Always needs a credential, in every access mode: the admin role (or root key) or an explicit `system/AuditEvent.read` scope. A wildcard `system/*` scope never covers AuditEvent.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "FHIR AuditEvent",
            "content": {
              "application/fhir+json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "description": "Credential required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FhirOperationOutcome"
                }
              }
            }
          },
          "403": {
            "description": "Credential lacks admin role or system/AuditEvent.read",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FhirOperationOutcome"
                }
              }
            }
          },
          "404": {
            "description": "FHIR OperationOutcome"
          },
          "501": {
            "description": "FHIR OperationOutcome (not-supported)"
          }
        }
      }
    },
    "/fhir/Bundle/$validate-document": {
      "post": {
        "tags": [
          "FHIR"
        ],
        "summary": "Validate an ADIME document Bundle",
        "description": "Checks a document Bundle (from $ncp-document or another system): Composition first and complete, the four ADIME sections, narrative, unique fullUrls, internal references, one patient, section placement and links between diagnosis, care plan, goals and follow-up. Returns 200 with an OperationOutcome of errors, warnings and, when valid, one information issue. Stateless: nothing is stored. Body is a FHIR Parameters resource with one `resource` parameter (the Bundle). Access: this operation needs a Bearer credential carrying the scope `system/Bundle.read`. A missing credential returns 401, a missing scope 403 (both as OperationOutcome).",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "resourceType",
                  "parameter"
                ],
                "properties": {
                  "resourceType": {
                    "type": "string",
                    "enum": [
                      "Parameters"
                    ]
                  },
                  "parameter": {
                    "type": "array",
                    "items": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "FHIR OperationOutcome (errors, warnings or a valid note)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "FHIR OperationOutcome - body is not Parameters or has no single resource",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "401": {
            "description": "Missing or invalid credential",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FhirOperationOutcome"
                }
              }
            }
          },
          "403": {
            "description": "Credential lacks the required FHIR scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FhirOperationOutcome"
                }
              }
            }
          }
        }
      }
    },
    "/fhir/Bundle/$nutrition-assessment": {
      "post": {
        "tags": [
          "FHIR"
        ],
        "summary": "Structured nutrition assessment (NCP step 1 foundation)",
        "description": "Validates a Patient (resource, or `subject` reference) and an Encounter (resource or reference, required) plus repeatable `observation` parts (anthropometry, biochemical, clinical, dietary, NFPE) and `questionnaireResponse` resources, and returns one collection Bundle of linked Patient, Encounter, QuestionnaireResponse and Observation resources (urn:uuid fullUrls; every Observation points at the Patient and Encounter). Units are never guessed; plausibility, timestamps and missing data are checked and all problems are reported together. Computes BMI (+ WHO adult class, 18+) and, from the nutrition-screening form, the MUST score. Stateless: nothing is stored. Body is a FHIR Parameters resource. See the API README, \"Assessment foundation\". Access: this operation needs a Bearer credential carrying the scope `system/Bundle.write`. A missing credential returns 401, a missing scope 403 (both as OperationOutcome).",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "resourceType",
                  "parameter"
                ],
                "properties": {
                  "resourceType": {
                    "type": "string",
                    "enum": [
                      "Parameters"
                    ]
                  },
                  "parameter": {
                    "type": "array",
                    "items": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "FHIR Bundle (collection); a trailing OperationOutcome entry lists warnings and missing-data notes",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "FHIR OperationOutcome — every validation error found (units, ranges, timestamps, required items, references)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "401": {
            "description": "Missing or invalid credential",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FhirOperationOutcome"
                }
              }
            }
          },
          "403": {
            "description": "Credential lacks the required FHIR scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FhirOperationOutcome"
                }
              }
            }
          }
        }
      }
    },
    "/fhir/QuestionnaireResponse/$validate": {
      "post": {
        "tags": [
          "FHIR"
        ],
        "summary": "Validate a QuestionnaireResponse against a built-in form",
        "description": "Body is a FHIR Parameters resource with one `resource` (the QuestionnaireResponse). Checks linkIds, answer types, units and ranges, answer options and required items for the built-in forms; other forms are checked structurally. Always 200 with an OperationOutcome when the input is well formed: look at issue severity. Nothing is built or stored. Access: this operation needs a Bearer credential carrying the scope `system/QuestionnaireResponse.read`. A missing credential returns 401, a missing scope 403 (both as OperationOutcome).",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "resourceType",
                  "parameter"
                ],
                "properties": {
                  "resourceType": {
                    "type": "string",
                    "enum": [
                      "Parameters"
                    ]
                  },
                  "parameter": {
                    "type": "array",
                    "items": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "FHIR OperationOutcome",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "FHIR OperationOutcome — body is not a Parameters resource, or 'resource' is missing",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "401": {
            "description": "Missing or invalid credential",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FhirOperationOutcome"
                }
              }
            }
          },
          "403": {
            "description": "Credential lacks the required FHIR scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FhirOperationOutcome"
                }
              }
            }
          }
        }
      }
    },
    "/fhir/Questionnaire": {
      "get": {
        "tags": [
          "FHIR"
        ],
        "summary": "List the built-in assessment forms",
        "description": "A searchset Bundle of the Questionnaires nutrition-screening, nfpe and diet-history.",
        "security": [],
        "responses": {
          "200": {
            "description": "FHIR Bundle (searchset)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/fhir/Questionnaire/{id}": {
      "get": {
        "tags": [
          "FHIR"
        ],
        "summary": "One built-in assessment form",
        "description": "nutrition-screening (the MUST inputs; submitting it to $nutrition-assessment also computes MUST), nfpe (nutrition-focused physical examination) or diet-history (24-hour recall / usual-day summary).\n",
        "security": [],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "nutrition-screening",
                "nfpe",
                "diet-history"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "FHIR Questionnaire",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "description": "FHIR OperationOutcome — unknown form",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/fhir/CodeSystem/ncp-diagnosis": {
      "get": {
        "tags": [
          "FHIR"
        ],
        "summary": "NCP nutrition diagnostic terminology (2006 edition) as a FHIR CodeSystem",
        "description": "62 terms (Intake NI, Clinical NC, Behavioral-Environmental NB) read from the Supabase table ncp_diagnosis_terms (sql/017). Used by Condition/$diagnose to resolve problemCode values such as NI-5.2.\n",
        "security": [],
        "responses": {
          "200": {
            "description": "FHIR CodeSystem",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/fhir/CodeSystem/{name}": {
      "get": {
        "tags": [
          "FHIR"
        ],
        "summary": "FHIR CodeSystem used by the facade",
        "description": "Static code lists behind the system URLs in FHIR responses (ncp-step, ncp-assessment-domain, glim, growth-status, ...). ncp-diagnosis is served from Supabase and has its own path above.\n",
        "security": [],
        "parameters": [
          {
            "name": "name",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "FHIR CodeSystem",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "description": "FHIR OperationOutcome — unknown name",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/fhir/OperationDefinition/{id}": {
      "get": {
        "tags": [
          "FHIR"
        ],
        "summary": "FHIR OperationDefinition",
        "description": "One per operation listed in /fhir/metadata. id is <Resource>-<operation>, e.g. Observation-screen, Condition-diagnose, Bundle-ncp-document.\n",
        "security": [],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "FHIR OperationDefinition",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "description": "FHIR OperationOutcome — unknown id",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/fhir/StructureDefinition/{name}": {
      "get": {
        "tags": [
          "FHIR"
        ],
        "summary": "FHIR Extension StructureDefinition",
        "description": "The Extensions the facade attaches to resources (e.g. pes-specify, goal-baseline, period-days).\n",
        "security": [],
        "parameters": [
          {
            "name": "name",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "FHIR StructureDefinition",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "description": "FHIR OperationOutcome — unknown name",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/packaged": {
      "get": {
        "tags": [
          "Packaged"
        ],
        "summary": "List packaged/branded foods",
        "parameters": [
          {
            "name": "barcode",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/Search"
          },
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Offset"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/ListOrCursor"
          }
        }
      }
    },
    "/packaged/pending": {
      "get": {
        "tags": [
          "Packaged"
        ],
        "summary": "Admin review queue (admin)",
        "description": "Rows with status \"pending\" from either submission path, oldest first.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "source",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "manual",
                "ocr_ai"
              ]
            }
          },
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Offset"
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/ListOrCursor"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/packaged/submit": {
      "post": {
        "tags": [
          "Packaged"
        ],
        "summary": "Submit a packaged food (API key, rate-limited)",
        "description": "Requires barcode + product_name. Auto-tagged status=pending. See the README for normalization (`per`/`serving_size`), duplicate detection (409 if an approved/pending row already has this barcode; overwritten in place if the existing row was rejected), and the macro/calorie cross-check (`needs_review`).\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "barcode",
                  "product_name"
                ],
                "properties": {
                  "barcode": {
                    "type": "string"
                  },
                  "product_name": {
                    "type": "string"
                  },
                  "per": {
                    "type": "string",
                    "enum": [
                      "100g",
                      "100ml",
                      "serving"
                    ]
                  },
                  "serving_size": {
                    "type": "string",
                    "example": "30g"
                  }
                },
                "additionalProperties": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/MutationSuccess"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "409": {
            "description": "Barcode already has an approved/pending entry - not inserted.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string",
                      "example": "success"
                    },
                    "message": {
                      "type": "string"
                    },
                    "already_exists": {
                      "type": "boolean",
                      "example": true
                    },
                    "data": {
                      "$ref": "#/components/schemas/PackagedFood"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/packaged/scan": {
      "post": {
        "tags": [
          "Packaged"
        ],
        "summary": "Submit a packaged food via photo (API key, rate-limited)",
        "description": "Sends 1-5 photos to a Groq vision model, combines what it reads across all of them, and inserts a pending row with source=ocr_ai. Returns 422 (status=needs_retry) without writing if no photo shows a legible nutrition label. Same duplicate-detection and macro/calorie-check behavior as /packaged/submit.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "images"
                ],
                "properties": {
                  "images": {
                    "type": "array",
                    "minItems": 1,
                    "maxItems": 5,
                    "items": {
                      "type": "string",
                      "description": "data:image/...;base64,... URL or bare base64 (assumed JPEG)."
                    }
                  },
                  "barcode": {
                    "type": "string",
                    "description": "Optional - takes priority over anything the model reads."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/MutationSuccess"
          },
          "422": {
            "description": "No photo showed a legible nutrition label - nothing written.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string",
                      "example": "needs_retry"
                    },
                    "message": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/packaged/{id}/approve": {
      "post": {
        "tags": [
          "Packaged"
        ],
        "summary": "Approve a pending packaged food (admin)",
        "description": "Moves status to \"approved\". Body may include field corrections applied in the same update. reviewed_by defaults to the calling API key's label; pass an explicit reviewed_by to override.\n",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "reviewed_by": {
                    "type": "string"
                  }
                },
                "additionalProperties": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/MutationSuccess"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/packaged/{id}/reject": {
      "post": {
        "tags": [
          "Packaged"
        ],
        "summary": "Reject a pending packaged food (admin)",
        "description": "Moves status to \"rejected\". Requires a reason string.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "reason"
                ],
                "properties": {
                  "reason": {
                    "type": "string"
                  },
                  "reviewed_by": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/MutationSuccess"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/packaged/{id}": {
      "put": {
        "tags": [
          "Packaged"
        ],
        "summary": "Replace a packaged food (admin)",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PackagedFood"
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/MutationSuccess"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "patch": {
        "tags": [
          "Packaged"
        ],
        "summary": "Partially update a packaged food (admin)",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PackagedFood"
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/MutationSuccess"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "delete": {
        "tags": [
          "Packaged"
        ],
        "summary": "Delete a packaged food (admin)",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/MutationSuccess"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/favorites": {
      "get": {
        "tags": [
          "Favorites"
        ],
        "summary": "List a user's favorites (API key, rate-limited)",
        "parameters": [
          {
            "$ref": "#/components/parameters/UserId"
          },
          {
            "$ref": "#/components/parameters/ResourceType"
          },
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Offset"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/ListOrCursor"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "post": {
        "tags": [
          "Favorites"
        ],
        "summary": "Save a favorite (API key, rate-limited, idempotent)",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/FavoriteRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/MutationSuccess"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "delete": {
        "tags": [
          "Favorites"
        ],
        "summary": "Remove a favorite (API key, rate-limited)",
        "description": "No error if the favorite doesn't exist - `removed:false` in the response instead.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/FavoriteRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/MutationSuccess"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/history": {
      "get": {
        "tags": [
          "History"
        ],
        "summary": "Recently viewed, most recent first (API key, rate-limited)",
        "parameters": [
          {
            "$ref": "#/components/parameters/UserId"
          },
          {
            "$ref": "#/components/parameters/ResourceType"
          },
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Offset"
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/ListOrCursor"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "post": {
        "tags": [
          "History"
        ],
        "summary": "Log a view (API key, rate-limited, opt-in)",
        "description": "Upserts on (user_id, resource_type, resource_id) - repeat views update viewed_at in place rather than creating duplicate rows. Nothing is tracked automatically; the client calls this explicitly.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/FavoriteRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/MutationSuccess"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/log": {
      "get": {
        "tags": [
          "Food Log"
        ],
        "summary": "List nutrition diary entries (API key, opt-in identity)",
        "description": "Same client-supplied user_id model as favorites/history - no server-side account system.\n",
        "parameters": [
          {
            "$ref": "#/components/parameters/UserId"
          },
          {
            "name": "date",
            "in": "query",
            "description": "YYYY-MM-DD. Filters to entries on that date.",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Offset"
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/ListOrCursor"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          }
        }
      },
      "post": {
        "tags": [
          "Food Log"
        ],
        "summary": "Log a food diary entry",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "user_id",
                  "meal_type",
                  "calories"
                ],
                "properties": {
                  "user_id": {
                    "type": "string"
                  },
                  "meal_type": {
                    "type": "string",
                    "enum": [
                      "breakfast",
                      "lunch",
                      "snack",
                      "dinner"
                    ]
                  },
                  "calories": {
                    "type": "number",
                    "minimum": 0
                  },
                  "food_name": {
                    "type": "string",
                    "nullable": true
                  },
                  "entry_date": {
                    "type": "string",
                    "format": "date",
                    "description": "Defaults to today."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/MutationSuccess"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          }
        }
      }
    },
    "/log/{id}": {
      "get": {
        "tags": [
          "Food Log"
        ],
        "summary": "Get a food log entry by id",
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/MutationSuccess"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "delete": {
        "tags": [
          "Food Log"
        ],
        "summary": "Delete a food log entry",
        "description": "Requires the same user_id the entry was logged under.",
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          },
          {
            "$ref": "#/components/parameters/UserId"
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/MutationSuccess"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/log/summary": {
      "get": {
        "tags": [
          "Food Log"
        ],
        "summary": "Daily or weekly calorie totals for a user",
        "description": "Daily gives one day's breakdown by meal; weekly gives a 7-day window ending on `date` (default today) with per-day totals, a per-meal breakdown, and the daily average.\n",
        "parameters": [
          {
            "$ref": "#/components/parameters/UserId"
          },
          {
            "name": "period",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "daily",
                "weekly"
              ],
              "default": "daily"
            }
          },
          {
            "name": "date",
            "in": "query",
            "description": "YYYY-MM-DD, the anchor/end date. Defaults to today.",
            "schema": {
              "type": "string",
              "format": "date"
            }
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/MutationSuccess"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          }
        }
      }
    },
    "/recipes/calculate": {
      "post": {
        "tags": [
          "Recipes"
        ],
        "summary": "Compute total and per-serving nutrition for a recipe (API key, rate-limited)",
        "description": "Resolves each ingredient to a food row (local table first, then the same local-to-external lookupFoodCascade used by /foods/lookup), converts quantity/unit to grams via each food's own serving-size data where possible, then scales and sums nutrients across the recipe. Pure calculation, no persistence. Rate-limited like /rag/ask since an unresolved ingredient can fan out to an external lookup.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "ingredients"
                ],
                "properties": {
                  "servings": {
                    "type": "number",
                    "default": 1,
                    "minimum": 0
                  },
                  "ingredients": {
                    "type": "array",
                    "minItems": 1,
                    "items": {
                      "type": "object",
                      "properties": {
                        "food_id": {
                          "type": "integer"
                        },
                        "food_name": {
                          "type": "string"
                        },
                        "quantity": {
                          "type": "number"
                        },
                        "unit": {
                          "type": "string",
                          "example": "cup"
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/MutationSuccess"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/meals/analyze": {
      "post": {
        "tags": [
          "Meals"
        ],
        "summary": "Analyze a meal's macro split, food groups, and (optionally) targets (API key, rate-limited)",
        "description": "Builds on the same ingredient resolution as /recipes/calculate but frames the result around one eaten meal: macronutrient % of calories (Atwater 4/4/9) compared against the standard adult IOM AMDR range, which core food groups are present/missing, and - only when `daily_targets` is supplied - how the meal compares to them. Never infers a personalized target itself; that should come from a clinician or the caller's own EER/macro calculation. Set `conditions` to also run clinical flags (e.g. \"food_allergy\" requires a matching `allergens` array).\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "ingredients"
                ],
                "properties": {
                  "meal_type": {
                    "type": "string",
                    "enum": [
                      "breakfast",
                      "lunch",
                      "snack",
                      "dinner"
                    ]
                  },
                  "ingredients": {
                    "type": "array",
                    "minItems": 1,
                    "items": {
                      "type": "object",
                      "properties": {
                        "food_id": {
                          "type": "integer"
                        },
                        "food_name": {
                          "type": "string"
                        },
                        "quantity": {
                          "type": "number"
                        },
                        "unit": {
                          "type": "string"
                        }
                      }
                    }
                  },
                  "conditions": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Clinical condition flags to evaluate, e.g. \"food_allergy\", \"diabetes\"."
                  },
                  "allergens": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Required when conditions includes \"food_allergy\"."
                  },
                  "daily_targets": {
                    "type": "object",
                    "additionalProperties": true,
                    "description": "Optional caller-supplied daily targets to compare the meal against."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/MutationSuccess"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/paediatric/energy": {
      "post": {
        "tags": [
          "Paediatric nutrition"
        ],
        "summary": "Paediatric energy requirements (API key)",
        "description": "Basal metabolic rate by Schofield (1985, needs height) and WHO (1985), multiplied by activity and stress factor ranges (activity_level or activity_factor, stress_condition or stress_factor, with a position low, mid, high or 0-1); DRI/FAO 2004 total energy expenditure in kcal/kg/day; and the IOM estimated energy requirement through the energy engine (pal_category needed from 3 years). Returns a recommended value and macronutrient grams by the AMDR for the age. Estimates for a dietitian or doctor to review.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "sex",
                  "weight_kg"
                ],
                "properties": {
                  "sex": {
                    "type": "string",
                    "enum": [
                      "male",
                      "female"
                    ]
                  },
                  "age_months": {
                    "type": "number"
                  },
                  "age_years": {
                    "type": "number"
                  },
                  "weight_kg": {
                    "type": "number"
                  },
                  "height_cm": {
                    "type": "number",
                    "description": "Length if under 2 years."
                  },
                  "pal_category": {
                    "type": "string",
                    "enum": [
                      "sedentary",
                      "low_active",
                      "active",
                      "very_active"
                    ]
                  },
                  "activity_level": {
                    "type": "string",
                    "enum": [
                      "confined_to_bed",
                      "light",
                      "moderate",
                      "vigorous"
                    ]
                  },
                  "activity_factor": {
                    "type": "number"
                  },
                  "stress_condition": {
                    "type": "string",
                    "enum": [
                      "starvation",
                      "surgery",
                      "infection_sepsis",
                      "closed_head_injury",
                      "trauma",
                      "burn",
                      "growth_failure"
                    ]
                  },
                  "stress_factor": {
                    "type": "number"
                  },
                  "activity_position": {
                    "type": "string"
                  },
                  "stress_position": {
                    "type": "string"
                  }
                }
              },
              "example": {
                "sex": "female",
                "age_years": 6,
                "weight_kg": 20,
                "height_cm": 115,
                "pal_category": "low_active"
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/MutationSuccess"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/paediatric/protein": {
      "post": {
        "tags": [
          "Paediatric nutrition"
        ],
        "summary": "Paediatric protein requirements (API key)",
        "description": "IOM protein RDA/AI for healthy growth and the sick-child dose (g/kg/day and g/day) for the age, with the one matching clinical_status (healthy or sick) as the recommendation. Send energy_kcal to see protein as a share of energy against the AMDR.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "weight_kg"
                ],
                "properties": {
                  "age_months": {
                    "type": "number"
                  },
                  "age_years": {
                    "type": "number"
                  },
                  "weight_kg": {
                    "type": "number"
                  },
                  "clinical_status": {
                    "type": "string",
                    "enum": [
                      "healthy",
                      "sick"
                    ]
                  },
                  "energy_kcal": {
                    "type": "number"
                  }
                }
              },
              "example": {
                "age_months": 30,
                "weight_kg": 12,
                "clinical_status": "sick"
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/MutationSuccess"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/paediatric/fluid": {
      "post": {
        "tags": [
          "Paediatric nutrition"
        ],
        "summary": "Paediatric maintenance fluid, Holliday-Segar (API key)",
        "description": "Daily maintenance fluid from weight: 100-150 mL/kg up to 10 kg, 1000 mL + 50 mL per kg above 10 kg to 20 kg, and 1500 mL + 20 mL per kg above 20 kg. Returns mL/day, mL/kg/day and mL/hour.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "weight_kg"
                ],
                "properties": {
                  "weight_kg": {
                    "type": "number"
                  }
                }
              },
              "example": {
                "weight_kg": 15
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/MutationSuccess"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/paediatric/preterm": {
      "post": {
        "tags": [
          "Paediatric nutrition"
        ],
        "summary": "Preterm infant requirements and growth monitoring (API key)",
        "description": "Enteral fluid (120-200 mL/kg/day), energy (110-130 kcal/kg/day) and protein by weight band, with the monitoring targets. Add monitoring to check initial weight loss (up to 15%, regained in 10-14 days), weight gain (15-20 g/kg/day), length (more than 1 cm/week) and head circumference (0.8-1 cm/week).",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "weight_g"
                ],
                "properties": {
                  "weight_g": {
                    "type": "number",
                    "description": "Birth or current weight in grams."
                  },
                  "monitoring": {
                    "type": "object",
                    "properties": {
                      "birth_weight_g": {
                        "type": "number"
                      },
                      "nadir_weight_g": {
                        "type": "number"
                      },
                      "days_to_regain_birth_weight": {
                        "type": "number"
                      },
                      "weight_g_start": {
                        "type": "number"
                      },
                      "weight_g_end": {
                        "type": "number"
                      },
                      "days": {
                        "type": "number"
                      },
                      "length_cm_start": {
                        "type": "number"
                      },
                      "length_cm_end": {
                        "type": "number"
                      },
                      "hc_cm_start": {
                        "type": "number"
                      },
                      "hc_cm_end": {
                        "type": "number"
                      }
                    }
                  }
                }
              },
              "example": {
                "weight_g": 1200
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/MutationSuccess"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/paediatric/growth-velocity": {
      "post": {
        "tags": [
          "Paediatric nutrition"
        ],
        "summary": "Growth velocity against the reference (API key)",
        "description": "Weight g/day and length or height mm/day over an interval, compared with the reference normal velocity for the age at the start (birth to 24 months by sex, then 2 to under 11 years). Send start and end values with days, or the velocity directly. Girls 3-6 months have no usable weight reference. For WHO z-scores use POST /growth/assess.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "sex",
                  "days"
                ],
                "properties": {
                  "sex": {
                    "type": "string",
                    "enum": [
                      "male",
                      "female"
                    ]
                  },
                  "age_months": {
                    "type": "number"
                  },
                  "age_years": {
                    "type": "number"
                  },
                  "days": {
                    "type": "number"
                  },
                  "weight_g_start": {
                    "type": "number"
                  },
                  "weight_g_end": {
                    "type": "number"
                  },
                  "height_cm_start": {
                    "type": "number"
                  },
                  "height_cm_end": {
                    "type": "number"
                  },
                  "weight_gain_g_per_day": {
                    "type": "number"
                  },
                  "length_mm_per_day": {
                    "type": "number"
                  }
                }
              },
              "example": {
                "sex": "male",
                "age_months": 2,
                "days": 30,
                "weight_g_start": 4200,
                "weight_g_end": 4800
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/MutationSuccess"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/paediatric/catch-up": {
      "post": {
        "tags": [
          "Paediatric nutrition"
        ],
        "summary": "Catch-up growth energy and protein (API key)",
        "description": "Weight-age method: catch-up kcal/kg/day = reference kcal/kg for the weight-age x ideal weight for length/height / actual weight, and the same for protein g/kg/day. The ideal weight is ideal_weight_kg, or for a child under 5 years with height_cm the WHO median weight-for-length/height is read from who_weight_for_length_height. Also gives the BMR x growth-failure stress factor (1.5-2) estimate, percent of ideal weight and the reference velocity. Severe wasting needs a stabilisation protocol first.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "sex",
                  "weight_kg"
                ],
                "properties": {
                  "sex": {
                    "type": "string",
                    "enum": [
                      "male",
                      "female"
                    ]
                  },
                  "age_months": {
                    "type": "number"
                  },
                  "age_years": {
                    "type": "number"
                  },
                  "weight_kg": {
                    "type": "number"
                  },
                  "height_cm": {
                    "type": "number"
                  },
                  "ideal_weight_kg": {
                    "type": "number"
                  },
                  "weight_age_months": {
                    "type": "number"
                  }
                }
              },
              "example": {
                "sex": "male",
                "age_months": 24,
                "weight_kg": 9,
                "height_cm": 82
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/MutationSuccess"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "422": {
            "description": "The WHO weight-for-length/height row could not be read. Send ideal_weight_kg instead."
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/paediatric/enteral-feeds": {
      "post": {
        "tags": [
          "Paediatric nutrition"
        ],
        "summary": "Age-specific enteral feed calculations (API key)",
        "description": "Initiation, advancement and goal for continuous or bolus feeds for the child's weight in the age group (0-12 months, 1-6 years, 7 years and over), steps and hours to goal, goal volume per day, and, with formula_kcal_per_ml, the energy delivered at goal and its share of energy_target_kcal.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "weight_kg"
                ],
                "properties": {
                  "age_months": {
                    "type": "number"
                  },
                  "age_years": {
                    "type": "number"
                  },
                  "weight_kg": {
                    "type": "number"
                  },
                  "route": {
                    "type": "string",
                    "enum": [
                      "continuous",
                      "bolus"
                    ]
                  },
                  "formula_kcal_per_ml": {
                    "type": "number"
                  },
                  "energy_target_kcal": {
                    "type": "number"
                  }
                }
              },
              "example": {
                "age_months": 8,
                "weight_kg": 8,
                "route": "continuous",
                "formula_kcal_per_ml": 0.67
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/MutationSuccess"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/paediatric/plan": {
      "post": {
        "tags": [
          "Paediatric nutrition"
        ],
        "summary": "Paediatric nutrition plan in one call (API key)",
        "description": "Energy, protein, fluid and macronutrient grams together. Add catch_up true (reads the WHO median weight for a child under 5 years with height_cm) or ideal_weight_kg for catch-up targets, and feeding_route for enteral feed rates. Same inputs as the single calculators.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "sex",
                  "weight_kg"
                ],
                "properties": {
                  "sex": {
                    "type": "string",
                    "enum": [
                      "male",
                      "female"
                    ]
                  },
                  "age_months": {
                    "type": "number"
                  },
                  "age_years": {
                    "type": "number"
                  },
                  "weight_kg": {
                    "type": "number"
                  },
                  "height_cm": {
                    "type": "number"
                  },
                  "pal_category": {
                    "type": "string"
                  },
                  "clinical_status": {
                    "type": "string",
                    "enum": [
                      "healthy",
                      "sick"
                    ]
                  },
                  "catch_up": {
                    "type": "boolean"
                  },
                  "ideal_weight_kg": {
                    "type": "number"
                  },
                  "feeding_route": {
                    "type": "string",
                    "enum": [
                      "continuous",
                      "bolus"
                    ]
                  }
                }
              },
              "example": {
                "sex": "female",
                "age_years": 6,
                "weight_kg": 20,
                "height_cm": 115,
                "pal_category": "low_active"
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/MutationSuccess"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/paediatric/reference": {
      "get": {
        "tags": [
          "Paediatric nutrition"
        ],
        "summary": "Paediatric reference tables and their sources (API key)",
        "description": "All tables behind the paediatric calculators. Use section to get one of: fluid, energy_equations, activity_factors, stress_factors, dri_fao_2004_tee, macronutrient_distribution, protein, preterm, growth_velocity, enteral_feeds, sources. /pediatric works as an alias for /paediatric.",
        "parameters": [
          {
            "name": "section",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/MutationSuccess"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          }
        }
      }
    },
    "/critical-care/protein-by-stress": {
      "post": {
        "tags": [
          "Critical care nutrition"
        ],
        "summary": "Protein by metabolic stress level (API key)",
        "description": "g/kg/day protein by stress level (0 normal 0.8, 1 mild 0.8-1, 2 moderate 1.2-1.5, 3 severe 1.6-2.5). Send weight_kg for g/day. Omit stress_level for the whole table. Compensate separately for wound, stoma or fistula losses. Not often above 16 g N (100 g protein) a day except in catabolic critical illness.",
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "stress_level": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 3
                  },
                  "weight_kg": {
                    "type": "number"
                  }
                }
              },
              "example": {
                "stress_level": 2,
                "weight_kg": 70
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/MutationSuccess"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/critical-care/npe-nitrogen-ratio": {
      "post": {
        "tags": [
          "Critical care nutrition"
        ],
        "summary": "Non-protein energy to nitrogen ratio (API key)",
        "description": "g N2 = g protein / 6.25; ratio = NPE kcal / g N2. Above 150:1 normal or mildly elevated, 100-150:1 moderate stress, 80-100:1 severe stress, below 80:1 is outside the source table.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "npe_kcal",
                  "protein_g"
                ],
                "properties": {
                  "npe_kcal": {
                    "type": "number",
                    "description": "Non-protein energy kcal per 24h"
                  },
                  "protein_g": {
                    "type": "number",
                    "description": "Protein g per 24h"
                  }
                }
              },
              "example": {
                "npe_kcal": 1500,
                "protein_g": 75
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/MutationSuccess"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/critical-care/nitrogen-balance": {
      "post": {
        "tags": [
          "Critical care nutrition"
        ],
        "summary": "Nitrogen output and stress grade (general ICU) (API key)",
        "description": "Urinary N = urine volume L x urinary urea mmol/L x 0.028 (urea-derived only); total urinary N = x 1.2; total N output = total urinary N + 4 g obligatory losses; graded against the urinary N2 table. Unreliable in unstable patients and does not count wound or fistula losses. Burn patients use a different formula.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "urine_volume_l",
                  "urinary_urea_mmol_l"
                ],
                "properties": {
                  "urine_volume_l": {
                    "type": "number"
                  },
                  "urinary_urea_mmol_l": {
                    "type": "number"
                  }
                }
              },
              "example": {
                "urine_volume_l": 2,
                "urinary_urea_mmol_l": 250
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/MutationSuccess"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/critical-care/carbohydrate": {
      "post": {
        "tags": [
          "Critical care nutrition"
        ],
        "summary": "Carbohydrate requirement reference (API key)",
        "description": "Usual 60-70% of NPE, 30-70% of total energy or 2-4 mg/kg/min; minimum 150-200 g/24h; maximum 6-7 mg/kg/min without metabolic stress and 4-5 mg/kg/min in severe stress or TPN. Send weight_kg for g/day. The source prints the usual figure as 3-5 g/kg/min, which cannot be right; it is flagged in the response.",
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "weight_kg": {
                    "type": "number"
                  }
                }
              },
              "example": {
                "weight_kg": 70
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/MutationSuccess"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/critical-care/fat": {
      "get": {
        "tags": [
          "Critical care nutrition"
        ],
        "summary": "Fat requirement reference (API key)",
        "description": "Usual 30-40% of non-protein energy; minimum 100-200 g/week for essential fatty acids; maximum 1-2 g/kg/day (ESPEN 2009). Acute pancreatitis: 0.8-1.5 g/kg via TPN, triglycerides under 12 mmol/L.",
        "responses": {
          "200": {
            "$ref": "#/components/responses/MutationSuccess"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          }
        }
      }
    },
    "/critical-care/propofol-fat": {
      "post": {
        "tags": [
          "Critical care nutrition"
        ],
        "summary": "Propofol dose and fat delivered (API key)",
        "description": "1 mL of propofol 1% holds 10 mg propofol and 0.1 g fat; 1 mL of propofol 2% holds 20 mg propofol and 0.1 g fat.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "propofol_percent",
                  "volume_ml"
                ],
                "properties": {
                  "propofol_percent": {
                    "type": "string",
                    "enum": [
                      "1",
                      "2"
                    ]
                  },
                  "volume_ml": {
                    "type": "number"
                  }
                }
              },
              "example": {
                "propofol_percent": "1",
                "volume_ml": 100
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/MutationSuccess"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/critical-care/cho-fat-ratio": {
      "get": {
        "tags": [
          "Critical care nutrition"
        ],
        "summary": "CHO:fat ratio of non-protein energy (API key)",
        "description": "70:30 for normal requirements; 60:40 for glucose intolerance (blood glucose above 10 mmol/L); 50:50 when weaning from the ventilator is difficult or in respiratory failure (high pCO2).",
        "responses": {
          "200": {
            "$ref": "#/components/responses/MutationSuccess"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          }
        }
      }
    },
    "/critical-care/electrolytes": {
      "post": {
        "tags": [
          "Critical care nutrition"
        ],
        "summary": "Phosphate, potassium and magnesium replacement reference (API key)",
        "description": "Reference dosing. Send electrolyte (phosphate, potassium or magnesium) for one, or omit it for all three.",
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "electrolyte": {
                    "type": "string",
                    "enum": [
                      "phosphate",
                      "potassium",
                      "magnesium"
                    ]
                  }
                }
              },
              "example": {
                "electrolyte": "potassium"
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/MutationSuccess"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/critical-care/potassium-deficit": {
      "post": {
        "tags": [
          "Critical care nutrition"
        ],
        "summary": "Total potassium deficit band from serum potassium (API key)",
        "description": "Serum K 3-4 mmol/L is a 100-200 mmol deficit; 2-3 mmol/L is 200-400 mmol. Serum K rises about 0.25 mmol/L for each 20 mmol KCl given IV.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "serum_k_mmol_l"
                ],
                "properties": {
                  "serum_k_mmol_l": {
                    "type": "number"
                  }
                }
              },
              "example": {
                "serum_k_mmol_l": 2.6
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/MutationSuccess"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/research/harmonise": {
      "post": {
        "tags": [
          "Research engine"
        ],
        "summary": "Standardise nutrition variables across one or several clinical studies (API key)",
        "description": "Puts anthropometry, dietary intake, biochemistry, clinical signs, diagnoses and outcomes on one set of names and units. Send records for one study, or studies[] (each with study_id, records and optionally its own columns, units, date_format) for a pooled table. Units are never guessed: a value with no unit is reported as assumed_canonical. Out-of-range values are flagged with the units in which they would be plausible and are not altered. Derived: BMI and WHO adult class, % weight change (positive = loss), MUAC status, macronutrient % energy and per kg, GLIM, length of stay, 28- and 90-day mortality. Diagnoses (free text, ICD-10, NCP codes) map to a fixed concept list; negation and ambiguity return status review. Up to 500 records, 150 columns. Screening and standardisation aid, not a diagnosis.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "study_id": {
                    "type": "string"
                  },
                  "records": {
                    "type": "array",
                    "maxItems": 500,
                    "items": {
                      "type": "object"
                    }
                  },
                  "studies": {
                    "type": "array",
                    "maxItems": 10,
                    "items": {
                      "type": "object",
                      "required": [
                        "study_id",
                        "records"
                      ],
                      "properties": {
                        "study_id": {
                          "type": "string"
                        },
                        "records": {
                          "type": "array",
                          "items": {
                            "type": "object"
                          }
                        },
                        "columns": {
                          "type": "object",
                          "description": "source column -> {variable, unit?, value_map?}"
                        },
                        "units": {
                          "type": "object",
                          "additionalProperties": {
                            "type": "string"
                          }
                        }
                      }
                    }
                  },
                  "unit_system": {
                    "type": "string",
                    "enum": [
                      "conventional",
                      "si"
                    ]
                  },
                  "target_units": {
                    "type": "object",
                    "additionalProperties": {
                      "type": "string"
                    }
                  },
                  "columns": {
                    "type": "object"
                  },
                  "units": {
                    "type": "object",
                    "additionalProperties": {
                      "type": "string"
                    }
                  },
                  "missing_codes": {
                    "type": "array",
                    "maxItems": 20,
                    "items": {}
                  },
                  "date_format": {
                    "type": "string",
                    "enum": [
                      "dmy",
                      "mdy",
                      "ymd"
                    ]
                  },
                  "population": {
                    "type": "string",
                    "enum": [
                      "adult",
                      "paediatric",
                      "mixed"
                    ]
                  },
                  "glim_bmi_cutoffs": {
                    "type": "string",
                    "enum": [
                      "default",
                      "asian"
                    ]
                  },
                  "muac_scheme": {
                    "type": "string",
                    "enum": [
                      "cmam",
                      "who_hiv_2008"
                    ]
                  },
                  "implausible": {
                    "type": "string",
                    "enum": [
                      "flag",
                      "null"
                    ]
                  },
                  "keep": {
                    "type": "array",
                    "maxItems": 20,
                    "items": {
                      "type": "string"
                    }
                  },
                  "assume_alive_without_death_date": {
                    "type": "boolean"
                  }
                }
              },
              "example": {
                "study_id": "QECH",
                "units": {
                  "albumin": "g/L"
                },
                "columns": {
                  "sex_code": {
                    "variable": "sex",
                    "value_map": {
                      "1": "male",
                      "2": "female"
                    }
                  }
                },
                "records": [
                  {
                    "id": "p1",
                    "age": 54,
                    "sex_code": 2,
                    "weight_lb": 150,
                    "height_cm": 165,
                    "albumin": 32,
                    "crp": "<5",
                    "diagnosis": "Severe acute malnutrition; HIV positive"
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/MutationSuccess"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/research/convert": {
      "post": {
        "tags": [
          "Research engine"
        ],
        "summary": "Convert one nutrition value between units, with a plausibility check (API key)",
        "description": "Send {variable, value, from, to?} or {conversions: [...]} (up to 100). to defaults to the canonical unit, or the SI unit when unit_system is si. The value is converted and marked plausible or not; it is never refused for being out of range.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "variable": {
                    "type": "string"
                  },
                  "value": {},
                  "from": {
                    "type": "string"
                  },
                  "to": {
                    "type": "string"
                  },
                  "unit_system": {
                    "type": "string",
                    "enum": [
                      "conventional",
                      "si"
                    ]
                  },
                  "conversions": {
                    "type": "array",
                    "maxItems": 100,
                    "items": {
                      "type": "object"
                    }
                  }
                }
              },
              "example": {
                "conversions": [
                  {
                    "variable": "glucose",
                    "value": 5.5,
                    "from": "mmol/L"
                  },
                  {
                    "variable": "hba1c",
                    "value": 48,
                    "from": "mmol/mol"
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/MutationSuccess"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/research/variables": {
      "get": {
        "tags": [
          "Research engine"
        ],
        "summary": "Data dictionary of standardised variables (API key)",
        "description": "Name, domain, kind, canonical and SI unit, accepted units, LOINC code (only where the API already registers one), plausibility range, aliases; plus the derived variables and their rules.",
        "parameters": [
          {
            "name": "domain",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "anthropometry",
                "biochemical",
                "clinical",
                "demographic",
                "diagnosis",
                "dietary",
                "nfpe",
                "outcome",
                "timeline"
              ]
            }
          },
          {
            "name": "q",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "search name, alias or label"
          },
          {
            "name": "name",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "one variable by name or alias"
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/MutationSuccess"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/research/reference": {
      "get": {
        "tags": [
          "Research engine"
        ],
        "summary": "Matching rules, unit factors, diagnosis concepts and outcome definitions (API key)",
        "parameters": [
          {
            "name": "section",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "rules",
                "units",
                "diagnoses",
                "outcomes",
                "limits"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/MutationSuccess"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/longitudinal/analyse": {
      "post": {
        "tags": [
          "Longitudinal outcomes"
        ],
        "summary": "Follow one participant's nutrition outcomes over repeated visits (API key)",
        "description": "Send one participant and their visits. Each visit returns weight, length/height, BMI, MUAC and head circumference, WHO z-scores (weight-, length/height-, weight-for-length/height-, BMI-, head-circumference- and MUAC-for-age, birth to 19 years), the adult BMI class and fixed MUAC status, change since the previous visit and since baseline, and data-quality flags. Per variable: a least-squares trend with a 95% interval. Per condition (stunting, wasting, underweight, thinness, overweight/obesity, low MUAC, oedema, adult under- and overnutrition): new onset, recovered, relapsed, persistent or never. Intake totals and any other numeric outcome ride along in intake and measures. Age comes from birth_date, else the visit age_months, else baseline_age_months moved forward (flagged as estimated). Nothing is stored. Up to 60 visits. Screening and research aid, not a diagnosis.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "visits"
                ],
                "properties": {
                  "id": {
                    "type": "string"
                  },
                  "sex": {
                    "type": "string",
                    "enum": [
                      "boys",
                      "girls",
                      "male",
                      "female"
                    ]
                  },
                  "birth_date": {
                    "type": "string",
                    "format": "date"
                  },
                  "baseline_age_months": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 1500
                  },
                  "measured": {
                    "type": "string",
                    "enum": [
                      "lying",
                      "standing"
                    ]
                  },
                  "visits": {
                    "type": "array",
                    "maxItems": 60,
                    "items": {
                      "type": "object",
                      "required": [
                        "date"
                      ],
                      "properties": {
                        "date": {
                          "type": "string",
                          "format": "date"
                        },
                        "age_months": {
                          "type": "integer"
                        },
                        "weight_kg": {
                          "type": "number"
                        },
                        "length_cm": {
                          "type": "number"
                        },
                        "height_cm": {
                          "type": "number"
                        },
                        "muac_cm": {
                          "type": "number"
                        },
                        "hc_cm": {
                          "type": "number"
                        },
                        "oedema": {
                          "type": "boolean"
                        },
                        "pregnant": {
                          "type": "boolean"
                        },
                        "intake": {
                          "type": "object",
                          "additionalProperties": {
                            "type": "number"
                          }
                        },
                        "measures": {
                          "type": "object",
                          "additionalProperties": {
                            "type": "number"
                          }
                        }
                      }
                    }
                  },
                  "options": {
                    "type": "object",
                    "properties": {
                      "implausible": {
                        "type": "string",
                        "enum": [
                          "exclude",
                          "keep"
                        ]
                      },
                      "muac_scheme": {
                        "type": "string",
                        "enum": [
                          "cmam",
                          "who_hiv_2008"
                        ]
                      },
                      "z_change_threshold": {
                        "type": "number",
                        "minimum": 0.1,
                        "maximum": 3
                      },
                      "recovery_consecutive_visits": {
                        "type": "integer",
                        "minimum": 1,
                        "maximum": 10
                      },
                      "population": {
                        "type": "string",
                        "enum": [
                          "child",
                          "adult"
                        ]
                      }
                    }
                  }
                }
              },
              "example": {
                "id": "C1",
                "sex": "boys",
                "birth_date": "2024-01-15",
                "visits": [
                  {
                    "date": "2024-07-15",
                    "weight_kg": 7.2,
                    "length_cm": 65,
                    "muac_cm": 14,
                    "intake": {
                      "energy_kcal": 600
                    }
                  },
                  {
                    "date": "2024-10-15",
                    "weight_kg": 7.6,
                    "length_cm": 67,
                    "muac_cm": 13.5,
                    "intake": {
                      "energy_kcal": 700
                    }
                  },
                  {
                    "date": "2025-01-15",
                    "weight_kg": 7.9,
                    "length_cm": 69,
                    "muac_cm": 12.2,
                    "measures": {
                      "hb": 9.1
                    }
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/MutationSuccess"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/longitudinal/cohort": {
      "post": {
        "tags": [
          "Longitudinal outcomes"
        ],
        "summary": "Analyse a cohort's nutrition outcomes over follow-up (API key)",
        "description": "Send up to 300 participants (3000 visits in all), each as for /longitudinal/analyse plus an optional group. Returns paired change with 95% intervals, status transition tables (first to last assessed visit), incidence and recovery for each condition, follow-up time, retention against a visit schedule (schedule_days, schedule_tolerance_days), a two-group comparison when participants carry a group, and optionally a flat participant-visit table for R or pandas (include_table). A participant that cannot be analysed is listed under excluded with the reason; the rest are still analysed. Nothing is stored.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "participants"
                ],
                "properties": {
                  "participants": {
                    "type": "array",
                    "maxItems": 300,
                    "items": {
                      "type": "object",
                      "required": [
                        "visits"
                      ],
                      "properties": {
                        "id": {
                          "type": "string"
                        },
                        "group": {
                          "type": "string"
                        },
                        "sex": {
                          "type": "string"
                        },
                        "birth_date": {
                          "type": "string",
                          "format": "date"
                        },
                        "baseline_age_months": {
                          "type": "integer"
                        },
                        "visits": {
                          "type": "array",
                          "maxItems": 60,
                          "items": {
                            "type": "object"
                          }
                        }
                      }
                    }
                  },
                  "options": {
                    "type": "object",
                    "properties": {
                      "schedule_days": {
                        "type": "array",
                        "maxItems": 20,
                        "items": {
                          "type": "integer",
                          "minimum": 0,
                          "maximum": 14600
                        },
                        "description": "scheduled visit days since each participant's first visit"
                      },
                      "schedule_tolerance_days": {
                        "type": "integer",
                        "minimum": 0,
                        "maximum": 180,
                        "default": 14
                      },
                      "include_visits": {
                        "type": "boolean",
                        "description": "full visit-by-visit detail for every participant"
                      },
                      "include_table": {
                        "type": "boolean",
                        "description": "flat participant-visit table"
                      },
                      "implausible": {
                        "type": "string",
                        "enum": [
                          "exclude",
                          "keep"
                        ]
                      },
                      "muac_scheme": {
                        "type": "string",
                        "enum": [
                          "cmam",
                          "who_hiv_2008"
                        ]
                      },
                      "z_change_threshold": {
                        "type": "number"
                      },
                      "recovery_consecutive_visits": {
                        "type": "integer"
                      },
                      "population": {
                        "type": "string",
                        "enum": [
                          "child",
                          "adult"
                        ]
                      }
                    }
                  }
                }
              },
              "example": {
                "participants": [
                  {
                    "id": "a",
                    "group": "intervention",
                    "sex": "girls",
                    "birth_date": "2023-06-01",
                    "visits": [
                      {
                        "date": "2024-06-01",
                        "muac_cm": 11.2
                      },
                      {
                        "date": "2024-09-01",
                        "muac_cm": 12.8
                      }
                    ]
                  },
                  {
                    "id": "b",
                    "group": "control",
                    "sex": "boys",
                    "birth_date": "2023-05-10",
                    "visits": [
                      {
                        "date": "2024-06-02",
                        "muac_cm": 11.9
                      },
                      {
                        "date": "2024-09-03",
                        "muac_cm": 12.0
                      }
                    ]
                  }
                ],
                "options": {
                  "schedule_days": [
                    0,
                    90
                  ],
                  "include_table": true
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/MutationSuccess"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/longitudinal/reference": {
      "get": {
        "tags": [
          "Longitudinal outcomes"
        ],
        "summary": "Request fields, variables, condition definitions, pattern rules, thresholds and limits (API key)",
        "parameters": [
          {
            "name": "section",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "input",
                "variables",
                "conditions",
                "patterns",
                "thresholds",
                "limits"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/MutationSuccess"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/malnutrition/analyse": {
      "post": {
        "tags": [
          "Malnutrition research"
        ],
        "summary": "Population-level malnutrition indicators from survey or study records (API key)",
        "description": "Send up to 2000 child records. Measurements are validated with the /research/harmonise rules and the data quality engine; z-scores are recomputed with the WHO standards (the same classifiers as GET /growth/assess), or taken from z_columns you supply (with z_scale, for example 100 for values in hundredths, and missing_codes) for secondary analysis of survey files. Implausible z-scores are excluded by the WHO flexible limits (or against the survey mean) and counted. Reports, for the age range (default 0 to 59 months): stunting, underweight, wasting, and overweight, each with moderate and severe parts, a Wilson or exact interval and, for children under 5, the UNICEF/WHO/World Bank prevalence level for stunting, wasting and overweight (very low to very high); thinness and overweight by BMI-for-age from 60 months when the range extends; global and severe acute malnutrition by WHZ, by MUAC (125 and 115 mm), by oedema and combined, with the WHZ/MUAC overlap; concurrent wasting and stunting and the Composite Index of Anthropometric Failure; mean z-scores; the same by age group, sex and up to three columns you name, with a chi-square test for difference between groups; expected case numbers for a population; and an optional flat table. design_effect widens intervals for cluster surveys; sampling weights are not applied. Nothing is stored. Research aid, not a diagnosis.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "records"
                ],
                "properties": {
                  "records": {
                    "type": "array",
                    "maxItems": 2000,
                    "items": {
                      "type": "object"
                    }
                  },
                  "columns": {
                    "type": "object",
                    "description": "source column -> {variable, unit?, value_map?}, as in /research/harmonise"
                  },
                  "units": {
                    "type": "object",
                    "additionalProperties": {
                      "type": "string"
                    }
                  },
                  "missing_codes": {
                    "type": "array",
                    "maxItems": 20,
                    "items": {}
                  },
                  "date_format": {
                    "type": "string",
                    "enum": [
                      "dmy",
                      "mdy",
                      "ymd"
                    ]
                  },
                  "z_columns": {
                    "type": "object",
                    "description": "indicator -> column of z-scores to use instead of recomputing",
                    "properties": {
                      "haz": {
                        "type": "string"
                      },
                      "waz": {
                        "type": "string"
                      },
                      "whz": {
                        "type": "string"
                      },
                      "baz": {
                        "type": "string"
                      }
                    }
                  },
                  "z_scale": {
                    "type": "number",
                    "default": 1,
                    "description": "divide supplied z-scores by this (100 for hundredths)"
                  },
                  "oedema": {
                    "type": "string",
                    "description": "column: yes/no, true/false, 1/0 or a grade"
                  },
                  "strata": {
                    "type": "array",
                    "maxItems": 3,
                    "items": {},
                    "description": "columns, or {variable, name?, cutpoints?}"
                  },
                  "population": {
                    "type": "object",
                    "properties": {
                      "children": {
                        "type": "integer"
                      }
                    },
                    "description": "gives expected prevalent case numbers"
                  },
                  "confidence_level": {
                    "type": "number",
                    "default": 0.95
                  },
                  "ci_method": {
                    "type": "string",
                    "enum": [
                      "wilson",
                      "exact"
                    ],
                    "default": "wilson"
                  },
                  "design_effect": {
                    "type": "number",
                    "default": 1
                  },
                  "options": {
                    "type": "object",
                    "properties": {
                      "age_range": {
                        "type": "array",
                        "items": {
                          "type": "integer"
                        },
                        "minItems": 2,
                        "maxItems": 2,
                        "default": [
                          0,
                          59
                        ]
                      },
                      "flag_rule": {
                        "type": "string",
                        "enum": [
                          "who",
                          "survey_mean",
                          "both"
                        ],
                        "default": "who"
                      },
                      "z_source": {
                        "type": "string",
                        "enum": [
                          "auto",
                          "supplied",
                          "recomputed"
                        ],
                        "default": "auto"
                      },
                      "z_tolerance": {
                        "type": "number",
                        "default": 0.1
                      },
                      "include_table": {
                        "type": "boolean",
                        "default": false
                      }
                    }
                  }
                }
              },
              "example": {
                "oedema": "oedema",
                "strata": [
                  "district"
                ],
                "confidence_level": 0.95,
                "columns": {
                  "age_months": {
                    "variable": "age-years",
                    "unit": "mo"
                  },
                  "weight_kg": {
                    "variable": "weight",
                    "unit": "kg"
                  },
                  "length_cm": {
                    "variable": "length",
                    "unit": "cm"
                  },
                  "height_cm": {
                    "variable": "height",
                    "unit": "cm"
                  },
                  "muac_mm": {
                    "variable": "muac",
                    "unit": "mm"
                  }
                },
                "records": [
                  {
                    "id": "K1",
                    "sex": "F",
                    "age_months": 14,
                    "weight_kg": 8.1,
                    "length_cm": 71,
                    "muac_mm": 128,
                    "oedema": "no",
                    "district": "Zomba"
                  },
                  {
                    "id": "K2",
                    "sex": "M",
                    "age_months": 30,
                    "weight_kg": 11.2,
                    "height_cm": 86.5,
                    "muac_mm": 137,
                    "oedema": "no",
                    "district": "Machinga"
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/MutationSuccess"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/malnutrition/reference": {
      "get": {
        "tags": [
          "Malnutrition research"
        ],
        "summary": "Case definitions, thresholds and limits (API key)",
        "parameters": [
          {
            "name": "section",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "indicators",
                "acute_malnutrition",
                "thresholds",
                "anthropometric_failure",
                "input",
                "limits"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/MutationSuccess"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/export/dataset": {
      "post": {
        "tags": [
          "Dataset export"
        ],
        "summary": "De-identified, analysis-ready dataset files with dictionary, provenance and loaders (API key)",
        "description": "Send up to 500 records (or several studies). Standardised with the /research/harmonise rules, then de-identified: keyed pseudonyms, relative dates, age generalisation and top-coding, k-anonymity measured (and optionally enforced by suppression). Returns files[] (CSV, dictionary CSV and JSON, Frictionless datapackage.json, R, Python and Stata loaders, provenance with SHA-256 hashes), disclosure_risk, columns_dropped and warnings. Nothing is stored. Not a certificate of anonymity.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "records": {
                    "type": "array",
                    "maxItems": 500,
                    "items": {
                      "type": "object"
                    }
                  },
                  "studies": {
                    "type": "array",
                    "maxItems": 10,
                    "items": {
                      "type": "object",
                      "required": [
                        "study_id",
                        "records"
                      ],
                      "properties": {
                        "study_id": {
                          "type": "string"
                        },
                        "records": {
                          "type": "array",
                          "items": {
                            "type": "object"
                          }
                        },
                        "columns": {
                          "type": "object"
                        },
                        "units": {
                          "type": "object"
                        }
                      }
                    },
                    "description": "instead of records: pooled studies"
                  },
                  "columns": {
                    "type": "object",
                    "description": "source column -> {variable, unit?, value_map?}, as in /research/harmonise"
                  },
                  "units": {
                    "type": "object",
                    "additionalProperties": {
                      "type": "string"
                    }
                  },
                  "target_units": {
                    "type": "object",
                    "additionalProperties": {
                      "type": "string"
                    }
                  },
                  "unit_system": {
                    "type": "string",
                    "enum": [
                      "conventional",
                      "si"
                    ]
                  },
                  "missing_codes": {
                    "type": "array",
                    "maxItems": 20,
                    "items": {}
                  },
                  "date_format": {
                    "type": "string",
                    "enum": [
                      "dmy",
                      "mdy",
                      "ymd"
                    ]
                  },
                  "population": {
                    "type": "string",
                    "enum": [
                      "adult",
                      "paediatric",
                      "mixed"
                    ]
                  },
                  "implausible": {
                    "type": "string",
                    "enum": [
                      "flag",
                      "null"
                    ]
                  },
                  "dataset": {
                    "type": "object",
                    "properties": {
                      "id": {
                        "type": "string"
                      },
                      "title": {
                        "type": "string"
                      },
                      "description": {
                        "type": "string"
                      },
                      "creators": {
                        "type": "array",
                        "items": {
                          "type": "string"
                        }
                      },
                      "licence": {
                        "type": "string"
                      },
                      "version": {
                        "type": "string"
                      },
                      "contact": {
                        "type": "string"
                      },
                      "keywords": {
                        "type": "array",
                        "items": {
                          "type": "string"
                        }
                      },
                      "funding": {
                        "type": "string"
                      },
                      "ethics_approval": {
                        "type": "string"
                      },
                      "access_conditions": {
                        "type": "string"
                      }
                    }
                  },
                  "deidentify": {
                    "type": "object",
                    "properties": {
                      "ids": {
                        "type": "string",
                        "enum": [
                          "pseudonymise",
                          "drop"
                        ],
                        "default": "pseudonymise"
                      },
                      "id_column": {
                        "type": "string"
                      },
                      "salt": {
                        "type": "string",
                        "minLength": 16,
                        "maxLength": 200,
                        "description": "secret; never returned or stored"
                      },
                      "dates": {
                        "type": "string",
                        "enum": [
                          "relative",
                          "year",
                          "drop"
                        ],
                        "default": "relative"
                      },
                      "age": {
                        "type": "string",
                        "enum": [
                          "months",
                          "years",
                          "band",
                          "exact",
                          "drop"
                        ],
                        "default": "months"
                      },
                      "k": {
                        "type": "integer",
                        "minimum": 2,
                        "maximum": 50,
                        "default": 5
                      },
                      "on_violation": {
                        "type": "string",
                        "enum": [
                          "report",
                          "suppress"
                        ],
                        "default": "report"
                      },
                      "quasi_identifiers": {
                        "type": "array",
                        "maxItems": 8,
                        "items": {
                          "type": "string"
                        }
                      },
                      "pseudonymise_columns": {
                        "type": "array",
                        "maxItems": 5,
                        "items": {
                          "type": "string"
                        }
                      }
                    }
                  },
                  "include": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "enum": [
                        "data",
                        "dictionary",
                        "dictionary_json",
                        "datapackage",
                        "provenance",
                        "r",
                        "python",
                        "stata"
                      ]
                    }
                  },
                  "include_columns": {
                    "type": "array",
                    "maxItems": 20,
                    "items": {
                      "type": "string"
                    },
                    "description": "input columns to copy through after identifier and free-text checks"
                  },
                  "include_qc": {
                    "type": "boolean",
                    "default": true
                  }
                }
              },
              "example": {
                "dataset": {
                  "id": "zomba-2024",
                  "title": "Zomba child nutrition survey",
                  "licence": "CC-BY-4.0"
                },
                "population": "paediatric",
                "include_columns": [
                  "district"
                ],
                "deidentify": {
                  "salt": "a-long-secret-you-keep",
                  "age": "band",
                  "quasi_identifiers": [
                    "district"
                  ],
                  "k": 5
                },
                "records": [
                  {
                    "id": "MW-1",
                    "sex": "F",
                    "age_months": 20,
                    "weight_kg": 9.4,
                    "hb": "<7 g/dL",
                    "visit_date": "2024-06-20",
                    "district": "Zomba"
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/MutationSuccess"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/export/verify": {
      "post": {
        "tags": [
          "Dataset export"
        ],
        "summary": "Check files against the SHA-256 hashes in a provenance file (API key)",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "files"
                ],
                "properties": {
                  "files": {
                    "type": "array",
                    "minItems": 1,
                    "maxItems": 12,
                    "items": {
                      "type": "object",
                      "required": [
                        "name",
                        "content"
                      ],
                      "properties": {
                        "name": {
                          "type": "string"
                        },
                        "content": {
                          "type": "string"
                        }
                      }
                    }
                  },
                  "provenance": {
                    "description": "the provenance file (object or JSON text); optional if it is among files"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/MutationSuccess"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/export/reference": {
      "get": {
        "tags": [
          "Dataset export"
        ],
        "summary": "Request fields, de-identification rules, file layouts, naming, provenance and limits (API key)",
        "parameters": [
          {
            "name": "section",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "request",
                "deidentification",
                "files",
                "naming",
                "provenance",
                "limits"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/MutationSuccess"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/data-quality/audit": {
      "post": {
        "tags": [
          "Data quality"
        ],
        "summary": "Audit a nutrition dataset for unit errors, implausible measurements and inconsistencies (API key)",
        "description": "Send up to 2000 records (a survey or study dataset). Units, ranges, missing codes and variable names are validated with the same rules as POST /research/harmonise (columns, units, missing_codes and date_format work the same way). Children with sex and age (a date of birth with an assessment date, or an age column) get WHO z-scores from the same classifiers as GET /growth/assess, and implausible ones are flagged against the WHO limits (haz -6/+6, waz -6/+5, whz -5/+5, baz -5/+5), or against the survey mean with options.flag_rule survey_mean. Consistency checks: age column against dates, visit before birth, future dates, weight above height (swapped columns), recorded BMI against weight and height, supplied z-scores (supplied_z) against recomputed ones, duplicate records, ids and visits. Survey-level quality: completeness, digit preference in weight, height and MUAC (terminal-digit counts, chi-square p and a chance-corrected heaping index), age heaping, sex ratio and age ratio (exact binomial tests), and the mean, SD, skewness and kurtosis of each z-score; group_by repeats these for each team or cluster and lists those to review. Suspected unit errors are listed with the unit in which the values would be plausible. Nothing is corrected: flagged values are left in the report and set to null in the optional analysis table (options.include_table). Screening aid, not a verdict; SMART/ENA plausibility scores are not reproduced. Nothing is stored.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "records"
                ],
                "properties": {
                  "records": {
                    "type": "array",
                    "maxItems": 2000,
                    "items": {
                      "type": "object"
                    }
                  },
                  "columns": {
                    "type": "object",
                    "description": "source column -> {variable, unit?, value_map?}, as in /research/harmonise"
                  },
                  "units": {
                    "type": "object",
                    "additionalProperties": {
                      "type": "string"
                    }
                  },
                  "missing_codes": {
                    "type": "array",
                    "maxItems": 20,
                    "items": {}
                  },
                  "date_format": {
                    "type": "string",
                    "enum": [
                      "dmy",
                      "mdy",
                      "ymd"
                    ]
                  },
                  "group_by": {
                    "type": "string",
                    "description": "column naming the team, cluster or enumerator (up to 50 groups)"
                  },
                  "supplied_z": {
                    "type": "object",
                    "description": "indicator -> column of z-scores from other software, e.g. {haz: haz_col}",
                    "additionalProperties": {
                      "type": "string"
                    }
                  },
                  "options": {
                    "type": "object",
                    "properties": {
                      "flag_rule": {
                        "type": "string",
                        "enum": [
                          "who",
                          "survey_mean",
                          "both"
                        ],
                        "default": "who"
                      },
                      "flag_range": {
                        "type": "number",
                        "minimum": 1,
                        "maximum": 6,
                        "default": 3,
                        "description": "z-score units from the survey mean (survey_mean rule)"
                      },
                      "z_tolerance": {
                        "type": "number",
                        "default": 0.1
                      },
                      "bmi_tolerance": {
                        "type": "number",
                        "default": 0.5
                      },
                      "sd_range": {
                        "type": "array",
                        "items": {
                          "type": "number"
                        },
                        "minItems": 2,
                        "maxItems": 2,
                        "default": [
                          0.8,
                          1.2
                        ]
                      },
                      "min_n": {
                        "type": "integer",
                        "minimum": 10,
                        "maximum": 1000,
                        "default": 30
                      },
                      "age_range": {
                        "type": "array",
                        "items": {
                          "type": "integer"
                        },
                        "minItems": 2,
                        "maxItems": 2,
                        "default": [
                          6,
                          59
                        ],
                        "description": "whole months, for sex ratio, age ratio and the survey summaries"
                      },
                      "age_ratio_expected": {
                        "type": "number"
                      },
                      "max_issue_records": {
                        "type": "integer",
                        "maximum": 2000,
                        "default": 200
                      },
                      "include_table": {
                        "type": "boolean",
                        "default": false
                      }
                    }
                  }
                }
              },
              "example": {
                "group_by": "team",
                "columns": {
                  "age_months": {
                    "variable": "age-years",
                    "unit": "mo"
                  },
                  "weight_kg": {
                    "variable": "weight",
                    "unit": "kg"
                  },
                  "length_cm": {
                    "variable": "length",
                    "unit": "cm"
                  },
                  "muac_mm": {
                    "variable": "muac",
                    "unit": "mm"
                  }
                },
                "options": {
                  "include_table": true
                },
                "records": [
                  {
                    "id": "C1",
                    "team": "T1",
                    "sex": "F",
                    "age_months": 14,
                    "weight_kg": 8.9,
                    "length_cm": 71.2,
                    "muac_mm": 138
                  },
                  {
                    "id": "C2",
                    "team": "T1",
                    "sex": "M",
                    "age_months": 20,
                    "weight_kg": 9400,
                    "length_cm": 79,
                    "muac_mm": 141
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/MutationSuccess"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/data-quality/reference": {
      "get": {
        "tags": [
          "Data quality"
        ],
        "summary": "What each check means, the screening bands and the limits (API key)",
        "parameters": [
          {
            "name": "section",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "checks",
                "survey_indicators",
                "input",
                "limits"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/MutationSuccess"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/epidemiology/analyse": {
      "post": {
        "tags": [
          "Nutrition epidemiology"
        ],
        "summary": "Prevalence, incidence, risk and odds ratios with confidence intervals from a research dataset (API key)",
        "description": "Send a dataset, the study design, an outcome and optionally an exposure and strata. The design decides what is reported. cross_sectional: prevalence with Wilson or exact intervals, prevalence ratio, difference and odds ratio. cohort: cumulative incidence (risk), risk ratio, difference and odds ratio; with a person_time column, incidence rates (exact Poisson interval) and rate ratios; baseline_outcome removes people who already have the outcome. case_control: the odds ratio only; prevalence, risk, incidence and risk ratios are refused. Outcome and exposure are binary rules on a column (positive values, or below / at_or_below / above / at_or_above / between for a numeric cut-off such as haz below -2). Unrecognised or missing values are excluded, counted and shown, never guessed. strata (up to 3, categories or numeric bands) give stratified prevalence, stratum-specific effect measures, Mantel-Haenszel pooled estimates, a Cochran-Mantel-Haenszel test, a heterogeneity test and a crude-versus-adjusted comparison; adjust_for cross-classifies several strata. indicators and means add other binary indicators and group means. design_effect widens intervals for cluster surveys; weights and clustering are not modelled. Up to 5000 records. Nothing is stored. Research aid, not a diagnosis.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "design",
                  "records",
                  "outcome"
                ],
                "properties": {
                  "design": {
                    "type": "string",
                    "enum": [
                      "cross_sectional",
                      "cohort",
                      "case_control"
                    ]
                  },
                  "records": {
                    "type": "array",
                    "maxItems": 5000,
                    "items": {
                      "type": "object"
                    }
                  },
                  "outcome": {
                    "$ref": "#/components/schemas/EpidemiologyRule"
                  },
                  "exposure": {
                    "allOf": [
                      {
                        "$ref": "#/components/schemas/EpidemiologyRule"
                      },
                      {
                        "type": "object",
                        "properties": {
                          "reference": {
                            "description": "compare every other level of this variable with this level (up to 10 levels)"
                          },
                          "cutpoints": {
                            "type": "array",
                            "items": {
                              "type": "number"
                            }
                          }
                        }
                      }
                    ]
                  },
                  "strata": {
                    "type": "array",
                    "maxItems": 3,
                    "items": {
                      "oneOf": [
                        {
                          "type": "string",
                          "description": "column name"
                        },
                        {
                          "type": "object",
                          "required": [
                            "variable"
                          ],
                          "properties": {
                            "variable": {
                              "type": "string"
                            },
                            "name": {
                              "type": "string"
                            },
                            "cutpoints": {
                              "type": "array",
                              "maxItems": 19,
                              "items": {
                                "type": "number"
                              },
                              "description": "numeric bands: [6, 24, 60] gives <6, 6 to <24, 24 to <60, 60+"
                            }
                          }
                        }
                      ]
                    }
                  },
                  "adjust_for": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "strata names to adjust for jointly (Mantel-Haenszel, up to 100 cells)"
                  },
                  "indicators": {
                    "type": "array",
                    "maxItems": 10,
                    "items": {
                      "$ref": "#/components/schemas/EpidemiologyRule"
                    }
                  },
                  "means": {
                    "type": "array",
                    "maxItems": 10,
                    "items": {
                      "type": "string"
                    }
                  },
                  "person_time": {
                    "type": "string",
                    "description": "cohort: column of follow-up time per participant"
                  },
                  "baseline_outcome": {
                    "description": "cohort: column positive when the outcome was already present at baseline"
                  },
                  "missing_codes": {
                    "type": "array",
                    "maxItems": 20,
                    "items": {}
                  },
                  "confidence_level": {
                    "type": "number",
                    "minimum": 0.5,
                    "maximum": 0.999,
                    "default": 0.95
                  },
                  "ci_method": {
                    "type": "string",
                    "enum": [
                      "wilson",
                      "exact"
                    ],
                    "default": "wilson"
                  },
                  "design_effect": {
                    "type": "number",
                    "minimum": 1,
                    "maximum": 20,
                    "default": 1
                  },
                  "rate_per": {
                    "type": "number",
                    "default": 1000
                  }
                }
              },
              "example": {
                "design": "cross_sectional",
                "confidence_level": 0.95,
                "outcome": {
                  "variable": "haz",
                  "below": -2
                },
                "exposure": {
                  "variable": "food_insecure",
                  "positive": "yes"
                },
                "strata": [
                  "sex",
                  {
                    "variable": "age_months",
                    "name": "age",
                    "cutpoints": [
                      6,
                      24
                    ]
                  }
                ],
                "records": [
                  {
                    "food_insecure": "yes",
                    "sex": "F",
                    "age_months": 14,
                    "haz": -2.4
                  },
                  {
                    "food_insecure": "no",
                    "sex": "M",
                    "age_months": 30,
                    "haz": -0.6
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/MutationSuccess"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/epidemiology/counts": {
      "post": {
        "tags": [
          "Nutrition epidemiology"
        ],
        "summary": "The same estimators from published or tabulated counts (API key)",
        "description": "For numbers from a paper or a table: proportions [{label, events, n}] with intervals; a 2x2 table {a, b, c, d} (a = exposed with the outcome, b = exposed without, c = unexposed with, d = unexposed without) with the measures the design permits; strata [{label, a, b, c, d}] for Mantel-Haenszel pooling, a heterogeneity test and a crude-versus-adjusted comparison; and rates {events_exposed, person_time_exposed, events_unexposed, person_time_unexposed} for a cohort. Counts must be whole numbers. Zero cells get 0.5 added for the ratios and the response says so.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "design"
                ],
                "properties": {
                  "design": {
                    "type": "string",
                    "enum": [
                      "cross_sectional",
                      "cohort",
                      "case_control"
                    ]
                  },
                  "proportions": {
                    "type": "array",
                    "maxItems": 50,
                    "items": {
                      "type": "object",
                      "properties": {
                        "label": {
                          "type": "string"
                        },
                        "events": {
                          "type": "integer"
                        },
                        "n": {
                          "type": "integer"
                        }
                      }
                    }
                  },
                  "table": {
                    "$ref": "#/components/schemas/EpidemiologyTable"
                  },
                  "strata": {
                    "type": "array",
                    "minItems": 2,
                    "maxItems": 50,
                    "items": {
                      "$ref": "#/components/schemas/EpidemiologyTable"
                    }
                  },
                  "rates": {
                    "type": "object",
                    "properties": {
                      "events_exposed": {
                        "type": "integer"
                      },
                      "person_time_exposed": {
                        "type": "number"
                      },
                      "events_unexposed": {
                        "type": "integer"
                      },
                      "person_time_unexposed": {
                        "type": "number"
                      }
                    }
                  },
                  "confidence_level": {
                    "type": "number",
                    "default": 0.95
                  },
                  "ci_method": {
                    "type": "string",
                    "enum": [
                      "wilson",
                      "exact"
                    ]
                  },
                  "design_effect": {
                    "type": "number",
                    "default": 1
                  },
                  "rate_per": {
                    "type": "number",
                    "default": 1000
                  }
                }
              },
              "example": {
                "design": "cohort",
                "table": {
                  "a": 20,
                  "b": 80,
                  "c": 10,
                  "d": 90
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/MutationSuccess"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/epidemiology/reference": {
      "get": {
        "tags": [
          "Nutrition epidemiology"
        ],
        "summary": "Design rules, measure definitions, methods and limits (API key)",
        "parameters": [
          {
            "name": "section",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "designs",
                "measures",
                "methods",
                "input",
                "limits"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/MutationSuccess"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/micronutrients/assess": {
      "post": {
        "tags": [
          "Micronutrient assessment"
        ],
        "summary": "Micronutrient intake assessment against the DRI (API key)",
        "description": "Age- and sex-specific screen of micronutrient intake. Below the EAR is likely inadequate, EAR to RDA is borderline, below an AI is possibly low, above the UL is flagged. Send intake as nutrient totals (one day, or days[] of up to 7, averaged). Nutrients not sent are listed in not_provided, not counted as zero. For foods use POST /dietary-assessment.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "age": {
                    "type": "number"
                  },
                  "sex": {
                    "type": "string",
                    "enum": [
                      "male",
                      "female"
                    ]
                  },
                  "life_stage": {
                    "type": "string"
                  },
                  "life_stage_type": {
                    "type": "string",
                    "enum": [
                      "normal",
                      "pregnancy",
                      "lactation"
                    ]
                  },
                  "plant_based_diet": {
                    "type": "boolean"
                  },
                  "intake": {
                    "type": "object",
                    "additionalProperties": {
                      "type": "number"
                    }
                  },
                  "days": {
                    "type": "array",
                    "maxItems": 7,
                    "items": {
                      "type": "object",
                      "properties": {
                        "date": {
                          "type": "string"
                        },
                        "intake": {
                          "type": "object",
                          "additionalProperties": {
                            "type": "number"
                          }
                        }
                      }
                    }
                  }
                }
              },
              "example": {
                "age": 25,
                "sex": "female",
                "plant_based_diet": true,
                "intake": {
                  "iron_mg": 9,
                  "calcium_mg": 600,
                  "vitc_mg": 40,
                  "zinc_mg": 6,
                  "vitb12_mcg": 1.5
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/MutationSuccess"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/micronutrients/requirements": {
      "get": {
        "tags": [
          "Micronutrient assessment"
        ],
        "summary": "Vitamin and mineral requirements by life stage (API key)",
        "description": "EAR, RDA, AI and UL for the life stage that matches age, sex and life_stage_type, or for a life_stage code.",
        "parameters": [
          {
            "name": "age",
            "in": "query",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "sex",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "male",
                "female"
              ]
            }
          },
          {
            "name": "life_stage",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "life_stage_type",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "normal",
                "pregnancy",
                "lactation"
              ]
            }
          },
          {
            "name": "group",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "vitamin",
                "mineral"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/MutationSuccess"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/micronutrients/reference": {
      "get": {
        "tags": [
          "Micronutrient assessment"
        ],
        "summary": "Micronutrients covered and the adequacy rules (API key)",
        "responses": {
          "200": {
            "$ref": "#/components/responses/MutationSuccess"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/enteral-nutrition/calculate": {
      "post": {
        "tags": [
          "Enteral nutrition"
        ],
        "summary": "Tube feeding prescription calculator (API key)",
        "description": "Volume, rate, energy, protein, formula water, free water flush and delivery adequacy, following the Edge Nutrition Tube Feeding Calculation Guide. The formula comes from formula_id or formula_name (enteral_formulas, read only) or from the body; body values override the row. Returns 422 if a looked-up row has no readable energy density.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "formula_id": {
                    "type": "string"
                  },
                  "formula_name": {
                    "type": "string"
                  },
                  "formula": {
                    "type": "object",
                    "properties": {
                      "kcal_per_ml": {
                        "type": "number"
                      },
                      "protein_g_per_l": {
                        "type": "number"
                      },
                      "water_ml_per_l": {
                        "type": "number"
                      }
                    }
                  },
                  "kcal_needs": {
                    "type": "number"
                  },
                  "protein_needs_g": {
                    "type": "number"
                  },
                  "fluid_needs_ml": {
                    "type": "number"
                  },
                  "weight_kg": {
                    "type": "number"
                  },
                  "medication_kcal": {
                    "type": "number"
                  },
                  "mode": {
                    "type": "string",
                    "enum": [
                      "continuous",
                      "cyclic",
                      "bolus",
                      "trophic",
                      "volume_based"
                    ]
                  },
                  "hours_per_day": {
                    "type": "number"
                  },
                  "feeds_per_day": {
                    "type": "number"
                  },
                  "trophic_rate_ml_hr": {
                    "type": "number"
                  },
                  "flush_every_hours": {
                    "type": "number"
                  },
                  "patient_type": {
                    "type": "string",
                    "enum": [
                      "adult",
                      "paediatric"
                    ]
                  },
                  "refeeding_risk": {
                    "type": "boolean"
                  },
                  "refeeding_kcal_per_kg": {
                    "type": "number"
                  },
                  "refeeding_start_pct": {
                    "type": "number"
                  },
                  "mechanically_ventilated": {
                    "type": "boolean"
                  },
                  "high_risk_tolerance": {
                    "type": "boolean"
                  },
                  "delivered_pct": {
                    "type": "number"
                  },
                  "delivered_volume_ml": {
                    "type": "number"
                  },
                  "protein_modular": {
                    "type": "object",
                    "properties": {
                      "protein_g_per_serving": {
                        "type": "number"
                      },
                      "kcal_per_serving": {
                        "type": "number"
                      }
                    }
                  }
                }
              },
              "example": {
                "kcal_needs": 1800,
                "protein_needs_g": 100,
                "fluid_needs_ml": 2100,
                "weight_kg": 70,
                "formula": {
                  "kcal_per_ml": 1.2,
                  "protein_g_per_l": 55.5,
                  "water_ml_per_l": 807
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/MutationSuccess"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/enteral-nutrition/reference": {
      "get": {
        "tags": [
          "Enteral nutrition"
        ],
        "summary": "Enteral nutrition steps, limits and request fields (API key)",
        "description": "Use section to get one of: steps, limits, quick_math, modes, request_fields.",
        "parameters": [
          {
            "name": "section",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/MutationSuccess"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/critical-care/fluid": {
      "post": {
        "tags": [
          "Critical care nutrition"
        ],
        "summary": "Adult ICU fluid reference range (API key)",
        "description": "30-35 mL/kg/day. In intensive care the treating doctor decides fluid requirements; this is a reference range only.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "weight_kg"
                ],
                "properties": {
                  "weight_kg": {
                    "type": "number"
                  }
                }
              },
              "example": {
                "weight_kg": 70
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/MutationSuccess"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/critical-care/reference": {
      "get": {
        "tags": [
          "Critical care nutrition"
        ],
        "summary": "Critical care reference tables and known issues (API key)",
        "description": "All tables behind the critical care calculators. Use section to get one of: protein_by_stress, urinary_nitrogen, npe_n2_bands, cho_fat_ratio, electrolytes, known_issues.",
        "parameters": [
          {
            "name": "section",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/MutationSuccess"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          }
        }
      }
    },
    "/burns/fluid-resuscitation": {
      "post": {
        "tags": [
          "Burn nutrition"
        ],
        "summary": "Burn resuscitation fluid, Parkland formula (API key)",
        "description": "24-hour crystalloid volume (4 mL x kg x %TBSA), half in the first 8 hours from injury and half over the next 16, with rates and, if hours_since_burn is sent, the volume left in the current window. Indicated above 10% TBSA.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "weight_kg",
                  "tbsa_burned_percent"
                ],
                "properties": {
                  "weight_kg": {
                    "type": "number"
                  },
                  "tbsa_burned_percent": {
                    "type": "number"
                  },
                  "hours_since_burn": {
                    "type": "number"
                  }
                }
              },
              "example": {
                "weight_kg": 70,
                "tbsa_burned_percent": 40
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/MutationSuccess"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "description": "The stored reference values are missing or unreadable."
          }
        }
      }
    },
    "/burns/energy": {
      "post": {
        "tags": [
          "Burn nutrition"
        ],
        "summary": "Burn energy requirement, Toronto (adult) or weight-only Schofield (child) (API key)",
        "description": "Adults (18 y and over) need tbsa_burned_percent, kcal_intake_past_24h, body_temp_c and days_post_burn for the Toronto equation; children use the weight-only Schofield equation. Indirect calorimetry is the gold standard.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "weight_kg",
                  "height_cm",
                  "age_years",
                  "sex"
                ],
                "properties": {
                  "weight_kg": {
                    "type": "number"
                  },
                  "height_cm": {
                    "type": "number"
                  },
                  "age_years": {
                    "type": "number"
                  },
                  "sex": {
                    "type": "string",
                    "enum": [
                      "male",
                      "female"
                    ]
                  },
                  "tbsa_burned_percent": {
                    "type": "number"
                  },
                  "kcal_intake_past_24h": {
                    "type": "number"
                  },
                  "body_temp_c": {
                    "type": "number"
                  },
                  "days_post_burn": {
                    "type": "number"
                  }
                }
              },
              "example": {
                "weight_kg": 70,
                "height_cm": 170,
                "age_years": 35,
                "sex": "male",
                "tbsa_burned_percent": 20,
                "kcal_intake_past_24h": 1500,
                "body_temp_c": 37.5,
                "days_post_burn": 3
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/MutationSuccess"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "description": "The stored reference values are missing or unreadable."
          }
        }
      }
    },
    "/burns/protein": {
      "post": {
        "tags": [
          "Burn nutrition"
        ],
        "summary": "Burn protein requirement (API key)",
        "description": "g/kg/day range and non-protein calorie to nitrogen ratio by burn size (adult), or the 1.5-3 g/kg/day paediatric range with is_pediatric.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "tbsa_burned_percent"
                ],
                "properties": {
                  "tbsa_burned_percent": {
                    "type": "number"
                  },
                  "weight_kg": {
                    "type": "number"
                  },
                  "is_pediatric": {
                    "type": "boolean"
                  }
                }
              },
              "example": {
                "tbsa_burned_percent": 35,
                "weight_kg": 70
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/MutationSuccess"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "description": "The stored reference values are missing or unreadable."
          }
        }
      }
    },
    "/burns/nitrogen-balance": {
      "post": {
        "tags": [
          "Burn nutrition"
        ],
        "summary": "Burn nitrogen balance (API key)",
        "description": "Nitrogen balance from protein intake and 24 h urine urea nitrogen, plus the Gottschlich estimate of nitrogen lost through burned tissue when open_wound_percent is sent.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "protein_intake_g",
                  "uun_g"
                ],
                "properties": {
                  "protein_intake_g": {
                    "type": "number"
                  },
                  "uun_g": {
                    "type": "number"
                  },
                  "open_wound_percent": {
                    "type": "number"
                  },
                  "weight_kg": {
                    "type": "number"
                  }
                }
              },
              "example": {
                "protein_intake_g": 100,
                "uun_g": 10,
                "open_wound_percent": 40,
                "weight_kg": 70
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/MutationSuccess"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "description": "The stored reference values are missing or unreadable."
          }
        }
      }
    },
    "/burns/micronutrients": {
      "post": {
        "tags": [
          "Burn nutrition"
        ],
        "summary": "Burn micronutrient dosing guidance (API key)",
        "description": "Vitamin and trace-element guidance by burn size, the vitamin A half-dose flag (BMI under 18, age under 18 or chronic malnutrition) and IV trace element duration.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "tbsa_burned_percent"
                ],
                "properties": {
                  "tbsa_burned_percent": {
                    "type": "number"
                  },
                  "bmi": {
                    "type": "number"
                  },
                  "age_years": {
                    "type": "number"
                  },
                  "chronically_malnourished": {
                    "type": "boolean"
                  },
                  "open_wound_percent": {
                    "type": "number"
                  }
                }
              },
              "example": {
                "tbsa_burned_percent": 30
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/MutationSuccess"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "description": "The stored reference values are missing or unreadable."
          }
        }
      }
    },
    "/burns/glycemic-targets": {
      "post": {
        "tags": [
          "Burn nutrition"
        ],
        "summary": "Burn carbohydrate ceiling and glucose targets (API key)",
        "description": "Carbohydrate limit (55-60% of energy, 5 mg/kg/min) and blood glucose targets (5-8 mmol/l) for burn patients.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "weight_kg": {
                    "type": "number"
                  },
                  "total_energy_kcal_per_day": {
                    "type": "number"
                  }
                }
              },
              "example": {
                "weight_kg": 70,
                "total_energy_kcal_per_day": 2000
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/MutationSuccess"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "description": "The stored reference values are missing or unreadable."
          }
        }
      }
    },
    "/burns/maintenance-fluid": {
      "post": {
        "tags": [
          "Burn nutrition"
        ],
        "summary": "Paediatric maintenance fluid, Holliday-Segar (API key)",
        "description": "Daily maintenance fluid (not resuscitation fluid): 100 mL/kg to 10 kg, then 50 mL/kg to 20 kg, then 20 mL/kg.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "weight_kg"
                ],
                "properties": {
                  "weight_kg": {
                    "type": "number"
                  }
                }
              },
              "example": {
                "weight_kg": 15
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/MutationSuccess"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "description": "The stored reference values are missing or unreadable."
          }
        }
      }
    },
    "/burns/indirect-calorimetry": {
      "post": {
        "tags": [
          "Burn nutrition"
        ],
        "summary": "REE from indirect calorimetry, modified Weir (API key)",
        "description": "REE from measured VO2 and VCO2, with the optional 1.4 activity factor and a 10-20% daytime adjustment.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "vo2_ml_per_min",
                  "vco2_ml_per_min"
                ],
                "properties": {
                  "vo2_ml_per_min": {
                    "type": "number"
                  },
                  "vco2_ml_per_min": {
                    "type": "number"
                  },
                  "apply_activity_factor": {
                    "type": "boolean"
                  },
                  "daytime_variability_percent": {
                    "type": "number"
                  },
                  "tbsa_burned_percent": {
                    "type": "number"
                  }
                }
              },
              "example": {
                "vo2_ml_per_min": 250,
                "vco2_ml_per_min": 200
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/MutationSuccess"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "description": "The stored reference values are missing or unreadable."
          }
        }
      }
    },
    "/burns/pediatric-equations": {
      "post": {
        "tags": [
          "Burn nutrition"
        ],
        "summary": "Paediatric burn predictive equations side by side (API key)",
        "description": "Curreri Junior, Galveston, Mayes, weight-only Schofield and WHO for the child, side by side; the first three need tbsa_burned_percent. All have historically over-predicted BMR.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "sex",
                  "age_years",
                  "weight_kg"
                ],
                "properties": {
                  "sex": {
                    "type": "string",
                    "enum": [
                      "male",
                      "female"
                    ]
                  },
                  "age_years": {
                    "type": "number"
                  },
                  "weight_kg": {
                    "type": "number"
                  },
                  "tbsa_burned_percent": {
                    "type": "number"
                  }
                }
              },
              "example": {
                "sex": "female",
                "age_years": 2,
                "weight_kg": 10,
                "tbsa_burned_percent": 20
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/MutationSuccess"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "description": "The stored reference values are missing or unreadable."
          }
        }
      }
    },
    "/burns/pharmacology": {
      "post": {
        "tags": [
          "Burn nutrition"
        ],
        "summary": "Burn hypermetabolism drug dosing reference (API key)",
        "description": "Growth hormone, IGF-1/IGFBP-3, insulin, propranolol and oxandrolone, with weight-scaled doses when weight_kg is sent. Includes the FDA oxandrolone withdrawal (23 June 2023).",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "weight_kg": {
                    "type": "number"
                  }
                }
              },
              "example": {
                "weight_kg": 20
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/MutationSuccess"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "description": "The stored reference values are missing or unreadable."
          }
        }
      }
    },
    "/burns/reference": {
      "get": {
        "tags": [
          "Burn nutrition"
        ],
        "summary": "Stored burn reference values (API key)",
        "description": "Lists the sections of clinical_reference_values (condition burns); use section to get the rows of one. The values can be edited in Supabase and reach the calculators within five minutes.",
        "parameters": [
          {
            "name": "section",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/MutationSuccess"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "503": {
            "description": "The stored reference values are missing or unreadable."
          }
        }
      }
    },
    "/refeeding/risk-adult": {
      "post": {
        "tags": [
          "Refeeding syndrome"
        ],
        "summary": "Refeeding risk, adults (ASPEN Table 3) (API key)",
        "description": "Moderate risk needs two criteria at moderate or higher; significant risk needs one at significant. BMI and weight loss are classified from numbers; the other criteria are the clinician-assessed level.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "bmi": {
                    "type": "number"
                  },
                  "weight_loss_percent": {
                    "type": "number"
                  },
                  "weight_loss_timeframe": {
                    "type": "string",
                    "enum": [
                      "1_month",
                      "3_months",
                      "6_months"
                    ]
                  },
                  "caloric_intake_level": {
                    "type": "string",
                    "enum": [
                      "none",
                      "moderate",
                      "significant"
                    ]
                  },
                  "prefeeding_electrolyte_abnormality_level": {
                    "type": "string",
                    "enum": [
                      "none",
                      "moderate",
                      "significant"
                    ]
                  },
                  "subcutaneous_fat_loss": {
                    "type": "string",
                    "enum": [
                      "none",
                      "moderate",
                      "significant"
                    ]
                  },
                  "muscle_mass_loss": {
                    "type": "string",
                    "enum": [
                      "none",
                      "moderate",
                      "significant"
                    ]
                  },
                  "comorbidity_severity": {
                    "type": "string",
                    "enum": [
                      "none",
                      "moderate",
                      "significant"
                    ]
                  }
                }
              },
              "example": {
                "bmi": 15,
                "caloric_intake_level": "moderate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/MutationSuccess"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "description": "The stored reference values are missing or unreadable."
          }
        }
      }
    },
    "/refeeding/risk-pediatric": {
      "post": {
        "tags": [
          "Refeeding syndrome"
        ],
        "summary": "Refeeding risk, paediatric (ASPEN Table 5) (API key)",
        "description": "Mild risk needs three criteria at mild or higher, moderate two at moderate or higher, significant one at significant. Not for neonates 28 days of life or younger.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "z_score_change_from_baseline": {
                    "type": "number"
                  },
                  "weight_gain_percent_of_expected": {
                    "type": "number"
                  },
                  "low_intake_days": {
                    "type": "number"
                  },
                  "muac_z_score": {
                    "type": "number"
                  },
                  "prefeeding_electrolyte_abnormality_level": {
                    "type": "string",
                    "enum": [
                      "none",
                      "mild",
                      "moderate",
                      "significant"
                    ]
                  },
                  "comorbidity_severity": {
                    "type": "string",
                    "enum": [
                      "none",
                      "mild",
                      "moderate",
                      "significant"
                    ]
                  },
                  "subcutaneous_fat_loss": {
                    "type": "string",
                    "enum": [
                      "none",
                      "mild",
                      "moderate",
                      "significant"
                    ]
                  },
                  "muscle_mass_loss": {
                    "type": "string",
                    "enum": [
                      "none",
                      "moderate",
                      "significant"
                    ]
                  }
                }
              },
              "example": {
                "low_intake_days": 6,
                "muac_z_score": -2.4
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/MutationSuccess"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "description": "The stored reference values are missing or unreadable."
          }
        }
      }
    },
    "/refeeding/severity": {
      "post": {
        "tags": [
          "Refeeding syndrome"
        ],
        "summary": "Refeeding syndrome severity (ASPEN definition) (API key)",
        "description": "Phosphorus, potassium and magnesium fall of 10-20% (mild), 20-30% (moderate) or over 30% (severe); organ dysfunction or thiamin deficiency is severe. Send baseline and current, or a percent decrease, for each electrolyte measured.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "phosphorus_baseline": {
                    "type": "number"
                  },
                  "phosphorus_current": {
                    "type": "number"
                  },
                  "phosphorus_percent_decrease": {
                    "type": "number"
                  },
                  "potassium_baseline": {
                    "type": "number"
                  },
                  "potassium_current": {
                    "type": "number"
                  },
                  "potassium_percent_decrease": {
                    "type": "number"
                  },
                  "magnesium_baseline": {
                    "type": "number"
                  },
                  "magnesium_current": {
                    "type": "number"
                  },
                  "magnesium_percent_decrease": {
                    "type": "number"
                  },
                  "organ_dysfunction_from_electrolyte_decrease": {
                    "type": "boolean"
                  },
                  "thiamin_deficiency": {
                    "type": "boolean"
                  },
                  "within_5_days_of_refeeding": {
                    "type": "boolean"
                  }
                }
              },
              "example": {
                "potassium_baseline": 4,
                "potassium_current": 3,
                "within_5_days_of_refeeding": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/MutationSuccess"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "description": "The stored reference values are missing or unreadable."
          }
        }
      }
    },
    "/refeeding/initiation": {
      "post": {
        "tags": [
          "Refeeding syndrome"
        ],
        "summary": "Refeeding initiation and advancement (ASPEN Tables 6 and 7) (API key)",
        "description": "Weight-based starting calories (adult) or starting percent of goal and glucose infusion rate (paediatric), advancement, electrolyte, thiamin and monitoring guidance.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "population",
                  "weight_kg"
                ],
                "properties": {
                  "population": {
                    "type": "string",
                    "enum": [
                      "adult",
                      "pediatric"
                    ]
                  },
                  "weight_kg": {
                    "type": "number"
                  },
                  "at_risk_for_rs": {
                    "type": "boolean"
                  },
                  "prefeeding_electrolytes_severely_low": {
                    "type": "boolean"
                  }
                }
              },
              "example": {
                "population": "adult",
                "weight_kg": 60
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/MutationSuccess"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "description": "The stored reference values are missing or unreadable."
          }
        }
      }
    },
    "/refeeding/reference": {
      "get": {
        "tags": [
          "Refeeding syndrome"
        ],
        "summary": "Stored refeeding reference values (API key)",
        "description": "Lists the sections of clinical_reference_values (condition refeeding); use section to get the rows of one. The values can be edited in Supabase and reach the calculators within five minutes.",
        "parameters": [
          {
            "name": "section",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/MutationSuccess"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "503": {
            "description": "The stored reference values are missing or unreadable."
          }
        }
      }
    },
    "/stress-factors/harris-benedict": {
      "post": {
        "tags": [
          "Energy and stress factors"
        ],
        "summary": "Harris-Benedict BEE with optional stress, adjustment and temperature factors (API key)",
        "description": "Basal energy expenditure from the Harris-Benedict equation (coefficients stored in clinical_reference_values). Optionally multiplies by one stress factor (use the highest applicable one, see GET /stress-factors/conditions), a 24-hour adjustment factor (see GET /stress-factors/adjustments) and a body temperature change of 13% per degree C from 37 C. Returns every step.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "sex",
                  "weight_kg",
                  "height_cm",
                  "age_years"
                ],
                "properties": {
                  "sex": {
                    "type": "string",
                    "enum": [
                      "male",
                      "female"
                    ]
                  },
                  "weight_kg": {
                    "type": "number"
                  },
                  "height_cm": {
                    "type": "number"
                  },
                  "age_years": {
                    "type": "number"
                  },
                  "stress_factor": {
                    "type": "number"
                  },
                  "adjustment_factor": {
                    "type": "number"
                  },
                  "body_temp_c": {
                    "type": "number"
                  }
                }
              },
              "example": {
                "sex": "male",
                "weight_kg": 70,
                "height_cm": 175,
                "age_years": 40,
                "stress_factor": 1.2
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/MutationSuccess"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "description": "The stored reference values are missing or unreadable."
          }
        }
      }
    },
    "/stress-factors/fever": {
      "post": {
        "tags": [
          "Energy and stress factors"
        ],
        "summary": "Fever adjustment of a baseline REE (API key)",
        "description": "Hardy and DuBois relationship as cited in Nelms/Ireton-Jones Ch. 2: about 7% per degree F above 98.6 F or 13% per degree C above 37 C. Send body_temp_f or body_temp_c (Fahrenheit is used when both are sent). A temperature at or below normal gives 0%.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "ree_kcal_per_day"
                ],
                "properties": {
                  "ree_kcal_per_day": {
                    "type": "number"
                  },
                  "body_temp_f": {
                    "type": "number"
                  },
                  "body_temp_c": {
                    "type": "number"
                  }
                }
              },
              "example": {
                "ree_kcal_per_day": 1500,
                "body_temp_c": 39
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/MutationSuccess"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "description": "The stored reference values are missing or unreadable."
          }
        }
      }
    },
    "/stress-factors/conditions": {
      "get": {
        "tags": [
          "Energy and stress factors"
        ],
        "summary": "Clinical stress factors (Barak et al, 2002) (API key)",
        "description": "The stress factor table (starvation, post-op, surgery, sepsis, trauma, pancreatitis, TBI, spinal cord injury, ICU, cancer, liver disease, wound healing and others). Send condition_key for one row. When more than one condition applies, use only the single highest factor.",
        "parameters": [
          {
            "name": "condition_key",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/MutationSuccess"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "503": {
            "description": "The stored reference values are missing or unreadable."
          }
        }
      }
    },
    "/stress-factors/adjustments": {
      "get": {
        "tags": [
          "Energy and stress factors"
        ],
        "summary": "24-hour energy expenditure adjustment factors (API key)",
        "description": "Ventilation (0.95), body temperature (13% per degree C from 37 C), sedatives and paralytics (0.75 to 1.00), activity when bedridden and awake (1.1 to 1.2) and very restless (1.2 to 1.45). Multiply with BEE, or with BEE x stress factor when one applies.",
        "responses": {
          "200": {
            "$ref": "#/components/responses/MutationSuccess"
          },
          "503": {
            "description": "The stored reference values are missing or unreadable."
          }
        }
      }
    },
    "/stress-factors/reference": {
      "get": {
        "tags": [
          "Energy and stress factors"
        ],
        "summary": "Stored stress factor reference values (API key)",
        "description": "Lists the sections of clinical_reference_values (condition stress_factors); use section to get the rows of one. The values can be edited in Supabase and reach the calculators within five minutes.",
        "parameters": [
          {
            "name": "section",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/MutationSuccess"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "503": {
            "description": "The stored reference values are missing or unreadable."
          }
        }
      }
    },
    "/carb-counting/carbohydrate-content": {
      "post": {
        "tags": [
          "Carbohydrate counting"
        ],
        "summary": "Carbohydrate in a weighed portion, or the portion weight for a carbohydrate target (API key)",
        "description": "Send carb_per_100g (the Carbohydrate line of the food label, not the of-which-sugars line) with exactly one of portion_weight_g (returns carb_in_portion_g) or target_carb_g (returns the portion weight needed).",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "carb_per_100g"
                ],
                "properties": {
                  "carb_per_100g": {
                    "type": "number"
                  },
                  "portion_weight_g": {
                    "type": "number"
                  },
                  "target_carb_g": {
                    "type": "number"
                  }
                }
              },
              "example": {
                "carb_per_100g": 60,
                "portion_weight_g": 150
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/MutationSuccess"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "description": "The stored reference values are missing or unreadable."
          }
        }
      }
    },
    "/carb-counting/insulin-dose": {
      "post": {
        "tags": [
          "Carbohydrate counting"
        ],
        "summary": "Mealtime quick-acting insulin dose, carbohydrate plus optional correction (API key)",
        "description": "Carb dose = (total_carb_g / 10) x icr_units_per_10g. To add a correction send current_bg_mmol_l with target_bg_low_mmol_l and target_bg_high_mmol_l: correction = (current - target midpoint) / isf_mmol_l_per_unit, with the ISF defaulting to the stored value (2.5 mmol/L per unit). A glucose below the target midpoint gives a correction of 0, never a negative one. Doses are rounded to the stored step (half a unit). Starting estimate only: the ratio, correction factor and targets belong to the person's diabetes team.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "total_carb_g",
                  "icr_units_per_10g"
                ],
                "properties": {
                  "total_carb_g": {
                    "type": "number"
                  },
                  "icr_units_per_10g": {
                    "type": "number"
                  },
                  "current_bg_mmol_l": {
                    "type": "number"
                  },
                  "target_bg_low_mmol_l": {
                    "type": "number"
                  },
                  "target_bg_high_mmol_l": {
                    "type": "number"
                  },
                  "isf_mmol_l_per_unit": {
                    "type": "number"
                  }
                }
              },
              "example": {
                "total_carb_g": 50,
                "icr_units_per_10g": 1,
                "current_bg_mmol_l": 12,
                "target_bg_low_mmol_l": 6,
                "target_bg_high_mmol_l": 8,
                "isf_mmol_l_per_unit": 2.5
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/MutationSuccess"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "description": "The stored reference values are missing or unreadable."
          }
        }
      }
    },
    "/carb-counting/foods": {
      "get": {
        "tags": [
          "Carbohydrate counting"
        ],
        "summary": "Food groups that do and do not affect blood glucose (API key)",
        "description": "Food groups that need quick-acting insulin matching and those that do not, with tips on non-starchy vegetables and sugary drinks. Send list to get only one of the two lists.",
        "parameters": [
          {
            "name": "list",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "affects_blood_glucose",
                "does_not_affect_blood_glucose"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/MutationSuccess"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "description": "The stored reference values are missing or unreadable."
          }
        }
      }
    },
    "/carb-counting/handy-measures": {
      "get": {
        "tags": [
          "Carbohydrate counting"
        ],
        "summary": "Handy carbohydrate measures and weighing rules (API key)",
        "description": "Everyday portions with their carbohydrate in grams (for example 1 thin slice of bread = 10 g) and the weighing rules of thumb: about half of bread weight, one third of cooked pasta, rice, noodles and chips, one fifth of a cooked jacket potato.",
        "responses": {
          "200": {
            "$ref": "#/components/responses/MutationSuccess"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "description": "The stored reference values are missing or unreadable."
          }
        }
      }
    },
    "/carb-counting/insulin-types": {
      "get": {
        "tags": [
          "Carbohydrate counting"
        ],
        "summary": "Background and quick-acting insulin types (API key)",
        "description": "Background insulin names and quick-acting insulin onset, peak and duration, with the timing note. Send category to get only one.",
        "parameters": [
          {
            "name": "category",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "background",
                "quick_acting"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/MutationSuccess"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "description": "The stored reference values are missing or unreadable."
          }
        }
      }
    },
    "/carb-counting/dose-adjustment": {
      "get": {
        "tags": [
          "Carbohydrate counting"
        ],
        "summary": "Stepwise carbohydrate counting and dose adjustment (API key)",
        "description": "The 7-step approach and the checklist for problem-solving out-of-range readings. Send section to get only one.",
        "parameters": [
          {
            "name": "section",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "stepwise_approach",
                "dose_adjustment_checklist"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/MutationSuccess"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "description": "The stored reference values are missing or unreadable."
          }
        }
      }
    },
    "/carb-counting/reference": {
      "get": {
        "tags": [
          "Carbohydrate counting"
        ],
        "summary": "Stored carb counting reference values (API key)",
        "description": "Lists the sections of clinical_reference_values (condition carb_counting); use section to get the rows of one. The values can be edited in Supabase and reach the calculators within five minutes.",
        "parameters": [
          {
            "name": "section",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/MutationSuccess"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "description": "The stored reference values are missing or unreadable."
          }
        }
      }
    },
    "/dietary-assessment": {
      "post": {
        "tags": [
          "Dietary assessment"
        ],
        "summary": "Dietary assessment - intake analysis, comparison with requirements, dietary gaps (API key, rate-limited)",
        "description": "Takes 1-7 days of intake and returns the average intake of every assessable nutrient against the DRI of the person's life stage, with ranked gaps and excesses. Intake comes from `meals`, `ingredients` (resolved with the same resolver as /meals/analyze, max 40 ingredients in total) or `intake` (nutrient totals, for example the total_nutrients from /meals/analyze, no food lookups). Nutrients with an EAR and RDA are judged below EAR (likely inadequate), between EAR and RDA (borderline) or adequate. Nutrients with only an AI are adequate or possibly low. Sodium is judged against the CDRR as a limit. The UL is flagged on average intake from food. Energy is compared with the caller's `energy_target_kcal` or the IOM EER (weight_kg, height_cm, pal_category). Macronutrient shares are compared with the AMDR for the age, and food-group coverage is reported per day. Each nutrient carries a confidence from the share of foods that have a value, so a low result caused by missing food-table data is marked. Unless `suggest_foods` is false, the foods richest in the top gap nutrients (per 100 g) are added. Eleven nutrients beyond those in /dri/compare are covered (thiamin, riboflavin, niacin, B6, E, pantothenic acid, biotin, phosphorus, selenium, copper, manganese). A screen against reference values, not a diagnosis.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "age": {
                    "type": "number"
                  },
                  "life_stage": {
                    "type": "string",
                    "description": "A code from GET /dri/life-stages. Alternative to age + sex."
                  },
                  "sex": {
                    "type": "string",
                    "enum": [
                      "male",
                      "female"
                    ]
                  },
                  "life_stage_type": {
                    "type": "string",
                    "enum": [
                      "normal",
                      "pregnancy",
                      "lactation"
                    ],
                    "default": "normal"
                  },
                  "weight_kg": {
                    "type": "number"
                  },
                  "height_cm": {
                    "type": "number"
                  },
                  "pal_category": {
                    "type": "string",
                    "enum": [
                      "sedentary",
                      "low_active",
                      "active",
                      "very_active"
                    ]
                  },
                  "trimester": {
                    "type": "integer",
                    "description": "1-3, adds the energy of pregnancy to the target."
                  },
                  "months_postpartum": {
                    "type": "number",
                    "description": "Adds the energy of lactation to the target."
                  },
                  "energy_target_kcal": {
                    "type": "number",
                    "description": "Overrides the calculated energy target."
                  },
                  "suggest_foods": {
                    "type": "boolean",
                    "default": true
                  },
                  "days": {
                    "type": "array",
                    "minItems": 1,
                    "maxItems": 7,
                    "description": "Each day has exactly one of meals, ingredients or intake. For a single day these may be sent at the top level instead.",
                    "items": {
                      "type": "object",
                      "properties": {
                        "date": {
                          "type": "string",
                          "format": "date"
                        },
                        "meals": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "meal_type": {
                                "type": "string"
                              },
                              "ingredients": {
                                "type": "array",
                                "items": {
                                  "type": "object",
                                  "properties": {
                                    "food_id": {
                                      "type": "integer"
                                    },
                                    "food_name": {
                                      "type": "string"
                                    },
                                    "quantity": {
                                      "type": "number"
                                    },
                                    "unit": {
                                      "type": "string"
                                    }
                                  }
                                }
                              }
                            }
                          }
                        },
                        "ingredients": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "food_id": {
                                "type": "integer"
                              },
                              "food_name": {
                                "type": "string"
                              },
                              "quantity": {
                                "type": "number"
                              },
                              "unit": {
                                "type": "string"
                              }
                            }
                          }
                        },
                        "intake": {
                          "type": "object",
                          "additionalProperties": {
                            "type": "number"
                          },
                          "example": {
                            "kcal": 1800,
                            "iron_mg": 9
                          }
                        }
                      }
                    }
                  }
                }
              },
              "example": {
                "age": 25,
                "sex": "female",
                "weight_kg": 60,
                "height_cm": 162,
                "pal_category": "low_active",
                "days": [
                  {
                    "date": "2026-10-20",
                    "meals": [
                      {
                        "meal_type": "lunch",
                        "ingredients": [
                          {
                            "food_name": "maize flour",
                            "quantity": 300
                          },
                          {
                            "food_name": "beans",
                            "quantity": 100
                          }
                        ]
                      }
                    ]
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/MutationSuccess"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "422": {
            "description": "No ingredient could be resolved; the body lists unresolved_ingredients."
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/dietary-assessment/reference": {
      "get": {
        "tags": [
          "Dietary assessment"
        ],
        "summary": "Assessable nutrients, status definitions, accepted input and method notes (API key)",
        "responses": {
          "200": {
            "$ref": "#/components/responses/MutationSuccess"
          }
        }
      }
    },
    "/ingredients/parse": {
      "post": {
        "tags": [
          "Ingredients"
        ],
        "summary": "Parse free-text ingredients into structured items (API key, rate-limited)",
        "description": "LLM-based extraction, e.g. \"2 eggs, 1 cup rice, 100g chicken and 1/2 avocado\" -> a structured ingredients array suitable for /recipes/calculate or /meals/analyze.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "text"
                ],
                "properties": {
                  "text": {
                    "type": "string",
                    "example": "2 eggs, 1 cup rice, 100g chicken and 1/2 avocado"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/MutationSuccess"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/dri/life-stages": {
      "get": {
        "tags": [
          "DRI"
        ],
        "summary": "List every DRI life-stage group",
        "responses": {
          "200": {
            "$ref": "#/components/responses/MutationSuccess"
          }
        }
      }
    },
    "/dri": {
      "get": {
        "tags": [
          "DRI"
        ],
        "summary": "Look up EAR/RDA/AI/UL by life stage or age+sex",
        "description": "Official Food and Nutrition Board (NASEM/IOM) reference values. Either pass `life_stage` (a code from GET /dri/life-stages), or `age` + `sex` (+ `life_stage_type` for pregnancy/lactation) - life_stage takes priority if both are given. Omit `nutrient` to get every nutrient for the resolved life stage.\n",
        "parameters": [
          {
            "name": "life_stage",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "age",
            "in": "query",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "sex",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "male",
                "female"
              ]
            }
          },
          {
            "name": "life_stage_type",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "normal",
                "pregnancy",
                "lactation"
              ],
              "default": "normal"
            }
          },
          {
            "name": "nutrient",
            "in": "query",
            "description": "Omit to return every nutrient for the resolved life stage.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/MutationSuccess"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          }
        }
      }
    },
    "/dri/compare": {
      "post": {
        "tags": [
          "DRI"
        ],
        "summary": "Compare an actual day's intake against a life stage's RDA/AI targets",
        "description": "Resolves a life stage the same way GET /dri does (life_stage code, or age/sex/life_stage_type), then flags each supplied intake value against its RDA/AI target and UL. Pass the total_nutrients object from POST /meals/analyze directly as `intake`.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "intake"
                ],
                "properties": {
                  "intake": {
                    "type": "object",
                    "additionalProperties": {
                      "type": "number"
                    },
                    "example": {
                      "calcium_mg": 850,
                      "iron_mg": 12
                    }
                  },
                  "life_stage": {
                    "type": "string"
                  },
                  "age": {
                    "type": "number"
                  },
                  "sex": {
                    "type": "string",
                    "enum": [
                      "male",
                      "female"
                    ]
                  },
                  "life_stage_type": {
                    "type": "string",
                    "enum": [
                      "normal",
                      "pregnancy",
                      "lactation"
                    ],
                    "default": "normal"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/MutationSuccess"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          }
        }
      }
    },
    "/rag/retrieve": {
      "post": {
        "tags": [
          "RAG"
        ],
        "summary": "Raw semantic retrieval (API key, rate-limited)",
        "description": "Returns matching rag_knowledge_base chunks without generating an answer.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "query"
                ],
                "properties": {
                  "query": {
                    "type": "string"
                  },
                  "context": {
                    "type": "string",
                    "enum": [
                      "clinical",
                      "general",
                      "both"
                    ],
                    "default": "both"
                  },
                  "top_k": {
                    "type": "integer",
                    "default": 5
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/MutationSuccess"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/rag/source": {
      "delete": {
        "tags": [
          "RAG"
        ],
        "summary": "Bulk-remove RAG chunks by citation (admin)",
        "description": "Removes every chunk whose `source` matches exactly. /rag/ingest only ever inserts (never upserts), so re-ingesting a fixed document leaves old bad chunks alongside the new ones unless cleared first via this route.\n",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "source",
            "in": "query",
            "required": true,
            "description": "The citation string, matching the --source used at ingest time.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/MutationSuccess"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/rag/ingest": {
      "post": {
        "tags": [
          "RAG"
        ],
        "summary": "Ingest a document into the RAG knowledge base (admin)",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "content",
                  "source"
                ],
                "properties": {
                  "content": {
                    "type": "string"
                  },
                  "source": {
                    "type": "string"
                  },
                  "context": {
                    "type": "string",
                    "enum": [
                      "clinical",
                      "general",
                      "both"
                    ],
                    "default": "both"
                  },
                  "metadata": {
                    "type": "object",
                    "additionalProperties": true
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/MutationSuccess"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/rag/ask": {
      "post": {
        "tags": [
          "RAG"
        ],
        "summary": "RAG Search Orchestrator (API key, rate-limited)",
        "description": "Full pipeline: intent detection (Groq) → fan-out search across semantic KB, Malawi FCT, packaged/OCR foods, exchange/renal/formula DBs, barcode lookup, and USDA/OFF/FatSecret fallback → rerank (Cohere) → grounded LLM answer with [n] citations. Whole answers are cached 5 minutes (skipped when session_id is passed).\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "query"
                ],
                "properties": {
                  "query": {
                    "type": "string"
                  },
                  "context": {
                    "type": "string",
                    "enum": [
                      "clinical",
                      "general",
                      "both"
                    ],
                    "default": "both"
                  },
                  "top_k": {
                    "type": "integer",
                    "default": 6
                  },
                  "session_id": {
                    "type": "string",
                    "description": "Optional - enables memory-personalized answers."
                  },
                  "history": {
                    "type": "array",
                    "maxItems": 6,
                    "description": "Optional - the last few chat turns, oldest first. Used only to work out what a short follow-up such as \"Compare with nsima\" refers to; the answer is still built only from retrieved sources. The last 6 entries are used and each text is cut to 400 characters.",
                    "items": {
                      "type": "object",
                      "required": [
                        "role",
                        "text"
                      ],
                      "properties": {
                        "role": {
                          "type": "string",
                          "enum": [
                            "user",
                            "assistant"
                          ]
                        },
                        "text": {
                          "type": "string"
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Grounded answer with citations",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Success"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "object",
                          "properties": {
                            "answer": {
                              "type": "string"
                            },
                            "intent": {
                              "type": "string",
                              "enum": [
                                "food_search",
                                "barcode_search",
                                "nutrition_question",
                                "exchange_list",
                                "enteral_formula",
                                "general_chat"
                              ]
                            },
                            "barcode_detected": {
                              "type": "string",
                              "nullable": true
                            },
                            "sources": {
                              "type": "array",
                              "items": {
                                "type": "object",
                                "properties": {
                                  "id": {
                                    "type": "integer"
                                  },
                                  "source": {
                                    "type": "string"
                                  },
                                  "title": {
                                    "type": "string"
                                  }
                                }
                              }
                            }
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/rag/upload": {
      "post": {
        "tags": [
          "RAG"
        ],
        "summary": "Upload a document for automatic ingestion (admin)",
        "description": "Multipart upload. The file is stored, an ingest job is created and queued; a background consumer extracts text, chunks it, embeds it and writes the chunks to the knowledge base. Poll `GET /rag/jobs/{id}` for status. Allowed types: pdf, docx, txt, md, csv, html. Maximum size 25 MB.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "file"
                ],
                "properties": {
                  "file": {
                    "type": "string",
                    "format": "binary"
                  },
                  "source": {
                    "type": "string",
                    "description": "Citation string stored with each chunk. Defaults to the file name without its extension."
                  },
                  "context": {
                    "type": "string",
                    "enum": [
                      "clinical",
                      "general",
                      "both"
                    ],
                    "default": "both"
                  },
                  "replace": {
                    "type": "boolean",
                    "description": "Remove previously ingested chunks for this document first.",
                    "default": false
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Job created and queued",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Success"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "413": {
            "description": "File larger than 25 MB",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "Storage upload failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Ingest queue is not bound on this deployment",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/rag/enqueue": {
      "post": {
        "tags": [
          "RAG"
        ],
        "summary": "Queue an already-uploaded file, or retry a failed job (admin)",
        "description": "Send `path` and `source` for a file already in the `rag-uploads` storage bucket, or send `job_id` to re-queue a failed job.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "path": {
                    "type": "string",
                    "description": "Object path inside the rag-uploads bucket."
                  },
                  "source": {
                    "type": "string",
                    "description": "Citation string. Required with `path`."
                  },
                  "context": {
                    "type": "string",
                    "enum": [
                      "clinical",
                      "general",
                      "both"
                    ],
                    "default": "both"
                  },
                  "replace": {
                    "type": "boolean",
                    "default": false
                  },
                  "job_id": {
                    "type": "string",
                    "description": "Retry an existing job instead of creating one."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/MutationSuccess"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "503": {
            "description": "Ingest queue is not bound on this deployment",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/rag/jobs": {
      "get": {
        "tags": [
          "RAG"
        ],
        "summary": "List recent ingestion jobs (admin)",
        "description": "Ingest jobs, newest first. Defaults to the 20 most recent; the response includes the total match count.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "queued",
                "processing",
                "done",
                "failed"
              ]
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 20,
              "maximum": 100
            }
          },
          {
            "name": "offset",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/MutationSuccess"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/rag/jobs/{id}": {
      "get": {
        "tags": [
          "RAG"
        ],
        "summary": "Get one ingestion job (admin)",
        "description": "Returns the job row: status (queued, processing, done, failed), attempts, chunk_count and any error.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/MutationSuccess"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/hooks/signup": {
      "post": {
        "tags": [
          "Developer"
        ],
        "summary": "Sign-up notification webhook (shared secret)",
        "description": "Called by the database trigger on `auth.users` (sql/044) when a developer signs up. Sends one Telegram message with the sign-up method and whether the address is confirmed. The email and user id are never sent. Authenticated with the `x-webhook-secret` header (`SIGNUP_WEBHOOK_SECRET`), not a bearer token. Events other than an INSERT on auth.users are skipped with a 200.",
        "security": [
          {
            "signupWebhookSecret": []
          }
        ],
        "parameters": [
          {
            "name": "x-webhook-secret",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "type",
                  "schema",
                  "table",
                  "record"
                ],
                "properties": {
                  "type": {
                    "type": "string",
                    "example": "INSERT"
                  },
                  "schema": {
                    "type": "string",
                    "example": "auth"
                  },
                  "table": {
                    "type": "string",
                    "example": "users"
                  },
                  "record": {
                    "type": "object",
                    "description": "The new auth.users row. Only id, raw_app_meta_data.provider and email_confirmed_at are read."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/MutationSuccess"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/rag/webhook": {
      "post": {
        "tags": [
          "RAG"
        ],
        "summary": "Storage trigger webhook (shared secret)",
        "description": "Called by the database storage trigger when a file lands in the `rag-uploads` bucket. Authenticated with the `x-webhook-secret` header, not a bearer token. Files under `api/` (already have a job) and unsupported types are skipped with a 200. Folder picks the context: `clinical/`, `general/`, anything else is `both`.",
        "security": [
          {
            "webhookSecret": []
          }
        ],
        "parameters": [
          {
            "name": "x-webhook-secret",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "bucket",
                  "name"
                ],
                "properties": {
                  "bucket": {
                    "type": "string",
                    "example": "rag-uploads"
                  },
                  "name": {
                    "type": "string",
                    "description": "Object path in the bucket."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/MutationSuccess"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "503": {
            "description": "INGEST_WEBHOOK_SECRET or the ingest queue is not configured",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/memory/write": {
      "post": {
        "tags": [
          "Memory"
        ],
        "summary": "Write a raw memory fact (API key, rate-limited)",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "session_id",
                  "content"
                ],
                "properties": {
                  "session_id": {
                    "type": "string"
                  },
                  "content": {
                    "type": "string"
                  },
                  "kind": {
                    "type": "string",
                    "default": "fact"
                  },
                  "patient_label": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/MutationSuccess"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/memory/recall": {
      "post": {
        "tags": [
          "Memory"
        ],
        "summary": "Recall top-K relevant memory rows for a session (API key, rate-limited, preferred)",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "session_id",
                  "query"
                ],
                "properties": {
                  "session_id": {
                    "type": "string"
                  },
                  "query": {
                    "type": "string"
                  },
                  "top_k": {
                    "type": "integer",
                    "default": 5
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/ListOrCursor"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "get": {
        "tags": [
          "Memory"
        ],
        "summary": "Recall (deprecated - use POST, same params as query string)",
        "description": "Deprecated: puts session_id and query text in the URL, exposed to access logs/browser history. Prefer POST /memory/recall.\n",
        "deprecated": true,
        "parameters": [
          {
            "name": "session_id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "query",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "top_k",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 5
            }
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/ListOrCursor"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/memory/consolidate": {
      "post": {
        "tags": [
          "Memory"
        ],
        "summary": "Manually trigger consolidation for one session (admin)",
        "description": "Also runs automatically every hour via cron. Sessions with fewer than 6 unconsolidated facts are skipped (status=\"skipped\").\n",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "session_id"
                ],
                "properties": {
                  "session_id": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/MutationSuccess"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/sports/reference": {
      "get": {
        "tags": [
          "Sports Nutrition"
        ],
        "summary": "Sports nutrition reference tables and known chapter issues",
        "description": "Activity factors, kcal/kg tiers, macronutrient targets by training profile, fuel timing, hydration guidance, body-fat standards (Table 23.1), sweat electrolytes (Table 23.3), pre-event meal tiers (Box 23.4) and the places where Krause chapter 23 contradicts itself. Any API key.",
        "responses": {
          "200": {
            "description": "Reference catalog"
          },
          "401": {
            "description": "Missing or invalid API key"
          }
        }
      }
    },
    "/sports/energy": {
      "post": {
        "tags": [
          "Sports Nutrition"
        ],
        "summary": "Athlete energy requirement (Cunningham REE, Box 23.2)",
        "description": "REE = 500 + 22 x lean body mass, times a Box 23.2 activity factor (or a custom multiplier), plus exercise energy from METs (0.0175 kcal/kg/min per MET) and/or an activity_log looked up in the Appendix 10 table, plus the thermic effect of food as its own 10% step. Returns every step, a kcal/kg cross-check against the chapter tiers and an optional Mifflin-St Jeor cross-check. This is the chapter 23 method: do not add its TEF to a /energy/requirements result.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "weight_kg": {
                    "type": "number",
                    "description": "30-250"
                  },
                  "lean_body_mass_kg": {
                    "type": "number",
                    "description": "Use this or body_fat_percent"
                  },
                  "body_fat_percent": {
                    "type": "number",
                    "description": "2-60"
                  },
                  "activity_level": {
                    "type": "string",
                    "enum": [
                      "sedentary",
                      "lightly_active",
                      "moderately_active",
                      "very_active",
                      "extra_active"
                    ]
                  },
                  "activity_multiplier": {
                    "type": "number",
                    "description": "1-2.5, instead of activity_level"
                  },
                  "exercise": {
                    "type": "array",
                    "maxItems": 20,
                    "items": {
                      "type": "object",
                      "properties": {
                        "mets": {
                          "type": "number"
                        },
                        "minutes": {
                          "type": "number"
                        },
                        "label": {
                          "type": "string"
                        }
                      }
                    }
                  },
                  "activity_log": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "activity": {
                          "type": "string"
                        },
                        "type": {
                          "type": "string"
                        },
                        "minutes": {
                          "type": "number"
                        },
                        "hours": {
                          "type": "number"
                        }
                      }
                    }
                  },
                  "tef": {
                    "type": "boolean",
                    "default": true
                  },
                  "sex": {
                    "type": "string",
                    "enum": [
                      "male",
                      "female"
                    ]
                  },
                  "age_years": {
                    "type": "number"
                  },
                  "height_cm": {
                    "type": "number"
                  }
                },
                "required": [
                  "weight_kg"
                ]
              },
              "example": {
                "weight_kg": 79.5,
                "lean_body_mass_kg": 71.6,
                "activity_level": "extra_active"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Calculation with steps, warnings and sources"
          },
          "400": {
            "description": "Invalid input"
          },
          "401": {
            "description": "Missing or invalid API key"
          },
          "429": {
            "description": "Rate limited"
          }
        }
      }
    },
    "/sports/macros": {
      "post": {
        "tags": [
          "Sports Nutrition"
        ],
        "summary": "Athlete macronutrient targets (g/kg by training profile)",
        "description": "Carbohydrate, protein and fat ranges in g/kg and grams, protein per meal, an optional energy split with an AMDR check (the table behind GET /dri), and an optional intake comparison.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "weight_kg": {
                    "type": "number"
                  },
                  "profile": {
                    "type": "string",
                    "enum": [
                      "general_fitness",
                      "general_training",
                      "endurance",
                      "elite_endurance",
                      "resistance_hypertrophy"
                    ]
                  },
                  "energy_kcal": {
                    "type": "number"
                  },
                  "age_years": {
                    "type": "number"
                  },
                  "intake": {
                    "type": "object",
                    "properties": {
                      "carbohydrate_g": {
                        "type": "number"
                      },
                      "protein_g": {
                        "type": "number"
                      },
                      "fat_g": {
                        "type": "number"
                      }
                    }
                  }
                },
                "required": [
                  "weight_kg",
                  "profile"
                ]
              },
              "example": {
                "weight_kg": 79,
                "profile": "general_training",
                "intake": {
                  "carbohydrate_g": 249,
                  "protein_g": 216,
                  "fat_g": 130
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Calculation with steps, warnings and sources"
          },
          "400": {
            "description": "Invalid input"
          },
          "401": {
            "description": "Missing or invalid API key"
          },
          "429": {
            "description": "Rate limited"
          }
        }
      }
    },
    "/sports/fuel-timing": {
      "post": {
        "tags": [
          "Sports Nutrition"
        ],
        "summary": "Carbohydrate before, during and after exercise",
        "description": "4 g/kg 4 hours before and 1 g/kg 1 hour before (never above 4 g/kg), Box 23.4 meal tiers, 25-30 g per 30 minutes during exercise over 60 minutes, recovery 1.2 g/kg/h with 5-9 g protein per 100 g carbohydrate, and a drink concentration check (under 5% unlikely to help, over 10% GI distress).",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "weight_kg": {
                    "type": "number"
                  },
                  "duration_min": {
                    "type": "number"
                  },
                  "hours_before_event": {
                    "type": "number"
                  },
                  "recovery_hours": {
                    "type": "number"
                  },
                  "drink": {
                    "type": "object",
                    "properties": {
                      "carbohydrate_g": {
                        "type": "number"
                      },
                      "serving_g": {
                        "type": "number"
                      },
                      "serving_ml": {
                        "type": "number"
                      }
                    }
                  }
                },
                "required": [
                  "weight_kg"
                ]
              },
              "example": {
                "weight_kg": 70,
                "duration_min": 120,
                "hours_before_event": 4,
                "recovery_hours": 4
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Calculation with steps, warnings and sources"
          },
          "400": {
            "description": "Invalid input"
          },
          "401": {
            "description": "Missing or invalid API key"
          },
          "429": {
            "description": "Rate limited"
          }
        }
      }
    },
    "/sports/hydration": {
      "post": {
        "tags": [
          "Sports Nutrition"
        ],
        "summary": "Hydration: body-mass loss, rehydration, in-exercise fluid and sodium",
        "description": "Body-mass loss % (2% can impair endurance, 3-4% team sports), rehydration at 25-50% more than the loss (450-675 mL per lb), 80% before a second session, in-exercise fluid and sodium (0.5-0.7 g/L over 1 hour), urine specific gravity band, child and master-athlete rules, heat warning. The sweat estimate is a standard mass balance, not a chapter formula. Daily needs: GET /dri?nutrient=water_l.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "weight_before_kg": {
                    "type": "number"
                  },
                  "weight_after_kg": {
                    "type": "number"
                  },
                  "fluid_intake_ml": {
                    "type": "number"
                  },
                  "urine_ml": {
                    "type": "number"
                  },
                  "duration_min": {
                    "type": "number"
                  },
                  "urine_specific_gravity": {
                    "type": "number"
                  },
                  "age_years": {
                    "type": "number"
                  },
                  "ambient_c": {
                    "type": "number"
                  },
                  "hours_to_next_session": {
                    "type": "number"
                  }
                }
              },
              "example": {
                "weight_before_kg": 70,
                "weight_after_kg": 68.6,
                "duration_min": 90,
                "hours_to_next_session": 3
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Calculation with steps, warnings and sources"
          },
          "400": {
            "description": "Invalid input"
          },
          "401": {
            "description": "Missing or invalid API key"
          },
          "429": {
            "description": "Rate limited"
          }
        }
      }
    },
    "/sports/body-composition": {
      "post": {
        "tags": [
          "Sports Nutrition"
        ],
        "summary": "Fat mass, lean mass, lowest safe weight and weekly rates",
        "description": "Fat and lean mass, lowest safe weight (lean mass / (1 - minimum body fat), 5% males / 12% females, 7% / 14% under 18, Table 23.1), highest optimal weight, healthy range, weekly loss (1-2 lb) and lean gain (0.5-1.0 lb males, 0.25-0.75 lb females) rates, and weeks to a target. No timeline is given for a target below the lowest safe weight.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "sex": {
                    "type": "string",
                    "enum": [
                      "male",
                      "female"
                    ]
                  },
                  "weight_kg": {
                    "type": "number"
                  },
                  "body_fat_percent": {
                    "type": "number"
                  },
                  "age_years": {
                    "type": "number"
                  },
                  "target_weight_kg": {
                    "type": "number"
                  }
                },
                "required": [
                  "sex",
                  "weight_kg",
                  "body_fat_percent"
                ]
              },
              "example": {
                "sex": "male",
                "weight_kg": 79.5,
                "body_fat_percent": 10,
                "target_weight_kg": 76
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Calculation with steps, warnings and sources"
          },
          "400": {
            "description": "Invalid input"
          },
          "401": {
            "description": "Missing or invalid API key"
          },
          "429": {
            "description": "Rate limited"
          }
        }
      }
    },
    "/sports/supplement-dose": {
      "post": {
        "tags": [
          "Sports Nutrition"
        ],
        "summary": "Weight-based supplement doses from Table 23.4",
        "description": "Caffeine (up to 3 mg/kg), creatine (loading and non-loading regimens), beta-alanine (65 mg/kg), HMB and beetroot juice. age_years is required. No doses are returned under 18, in pregnancy or when breastfeeding because the chapter gives none; creatine is specifically advised against for those groups. Not a substitute for clinical advice; check products against the WADA list.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "weight_kg": {
                    "type": "number"
                  },
                  "age_years": {
                    "type": "number"
                  },
                  "supplements": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "enum": [
                        "caffeine",
                        "creatine",
                        "beta_alanine",
                        "hmb",
                        "beetroot_juice"
                      ]
                    }
                  },
                  "pregnant": {
                    "type": "boolean"
                  },
                  "lactating": {
                    "type": "boolean"
                  }
                },
                "required": [
                  "weight_kg",
                  "age_years"
                ]
              },
              "example": {
                "weight_kg": 68,
                "age_years": 25,
                "supplements": [
                  "caffeine",
                  "creatine"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Calculation with steps, warnings and sources"
          },
          "400": {
            "description": "Invalid input"
          },
          "401": {
            "description": "Missing or invalid API key"
          },
          "429": {
            "description": "Rate limited"
          }
        }
      }
    },
    "/sports/plan": {
      "post": {
        "tags": [
          "Sports Nutrition"
        ],
        "summary": "One-athlete sports nutrition plan",
        "description": "Runs every calculator the body has inputs for and returns each as its own section, exactly as its own endpoint would. The macro section uses the energy section total unless energy_kcal is given. A bad value in one section only errors that section (see errors and not_assessed).",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "sex": {
                    "type": "string"
                  },
                  "age_years": {
                    "type": "number"
                  },
                  "height_cm": {
                    "type": "number"
                  },
                  "weight_kg": {
                    "type": "number"
                  },
                  "body_fat_percent": {
                    "type": "number"
                  },
                  "lean_body_mass_kg": {
                    "type": "number"
                  },
                  "activity_level": {
                    "type": "string"
                  },
                  "activity_multiplier": {
                    "type": "number"
                  },
                  "exercise": {
                    "type": "array",
                    "items": {
                      "type": "object"
                    }
                  },
                  "activity_log": {
                    "type": "array",
                    "items": {
                      "type": "object"
                    }
                  },
                  "profile": {
                    "type": "string"
                  },
                  "energy_kcal": {
                    "type": "number"
                  },
                  "intake": {
                    "type": "object"
                  },
                  "event": {
                    "type": "object",
                    "description": "duration_min, hours_before_event, recovery_hours, drink"
                  },
                  "hydration": {
                    "type": "object",
                    "description": "weight_before_kg, weight_after_kg, ..."
                  },
                  "weight_goal": {
                    "type": "object",
                    "description": "target_weight_kg"
                  },
                  "supplements": {
                    "type": "object",
                    "description": "supplements?, pregnant?, lactating?"
                  }
                }
              },
              "example": {
                "sex": "male",
                "age_years": 30,
                "weight_kg": 79.5,
                "body_fat_percent": 10,
                "activity_level": "very_active",
                "profile": "endurance",
                "event": {
                  "duration_min": 120,
                  "hours_before_event": 4
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Calculation with steps, warnings and sources"
          },
          "400": {
            "description": "Invalid input"
          },
          "401": {
            "description": "Missing or invalid API key"
          },
          "429": {
            "description": "Rate limited"
          }
        }
      }
    },
    "/weight-management/reference": {
      "get": {
        "tags": [
          "Weight Management"
        ],
        "summary": "Weight management reference tables",
        "description": "Krause chapter 21 tables: BMI classes, Table 21.3 disease risk, metabolic syndrome, rate bands, energy floors, AMDR, diet adequacy, activity targets, Table 21.11 and 21.12, and the places the chapter contradicts itself. Optional section filter.",
        "parameters": [
          {
            "name": "section",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "bmi_classes",
                "disease_risk",
                "body_fat",
                "metabolic_syndrome",
                "rates",
                "energy_floors",
                "macronutrients",
                "diets",
                "activity",
                "treatment",
                "unintentional_loss",
                "energy_boosters",
                "bariatric",
                "known_issues"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Reference"
          },
          "400": {
            "description": "Unknown section"
          },
          "401": {
            "description": "Missing or invalid API key"
          }
        }
      }
    },
    "/weight-management/bariatric": {
      "get": {
        "tags": [
          "Weight Management"
        ],
        "summary": "Bariatric diet stages and supplementation (Tables 21.9 and 21.10)",
        "description": "Diet progression (sleeve gastrectomy and Roux-en-Y bypass) and vitamin/mineral supplementation by procedure. Misprinted values in Table 21.10 are returned as printed with suspect: true, not corrected.",
        "parameters": [
          {
            "name": "procedure",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "lagb",
                "lsg",
                "rygb",
                "bpd_ds"
              ]
            }
          },
          {
            "name": "sex",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "male",
                "female"
              ]
            }
          },
          {
            "name": "childbearing_age",
            "in": "query",
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "pregnant",
            "in": "query",
            "schema": {
              "type": "boolean"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Guidance"
          },
          "400": {
            "description": "Invalid input"
          },
          "401": {
            "description": "Missing or invalid API key"
          }
        }
      }
    },
    "/weight-management/assess": {
      "post": {
        "tags": [
          "Weight Management"
        ],
        "summary": "Assess weight status: BMI class, disease risk, metabolic syndrome, treatment options",
        "description": "Adults (19 and over): BMI class, Table 21.3 disease risk with waist circumference, waist-hip ratio, body-fat obesity, metabolic syndrome (3 of 5), older-adult flags, unintentional weight-loss significance (the same Blackburn rule as $assess-nutrition) and treatment-option eligibility flags. Children and adolescents (2 to under 19): BMI-for-age percentile band (looked up in the WHO tables for ages 3-18 when weight and height are given) and the chapter's age-based goals. Eligibility flags are screening aids, not prescriptions.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "sex": {
                    "type": "string",
                    "enum": [
                      "male",
                      "female"
                    ]
                  },
                  "age_years": {
                    "type": "number"
                  },
                  "weight_kg": {
                    "type": "number"
                  },
                  "height_cm": {
                    "type": "number"
                  },
                  "waist_cm": {
                    "type": "number"
                  },
                  "hip_cm": {
                    "type": "number"
                  },
                  "body_fat_percent": {
                    "type": "number"
                  },
                  "usual_weight_kg": {
                    "type": "number"
                  },
                  "period_days": {
                    "type": "number"
                  },
                  "comorbidity_count": {
                    "type": "number"
                  },
                  "systolic_mmhg": {
                    "type": "number"
                  },
                  "diastolic_mmhg": {
                    "type": "number"
                  },
                  "triglycerides_mg_dl": {
                    "type": "number"
                  },
                  "hdl_mg_dl": {
                    "type": "number"
                  },
                  "fasting_glucose_mg_dl": {
                    "type": "number"
                  },
                  "bmi_for_age_percentile": {
                    "type": "number",
                    "description": "Children"
                  },
                  "secondary_complications": {
                    "type": "boolean",
                    "description": "Children"
                  }
                },
                "required": [
                  "sex",
                  "age_years"
                ]
              },
              "example": {
                "sex": "female",
                "age_years": 45,
                "weight_kg": 88.45,
                "height_cm": 162.56,
                "waist_cm": 100,
                "systolic_mmhg": 160,
                "diastolic_mmhg": 90
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Result with steps, warnings and sources"
          },
          "400": {
            "description": "Invalid input, or a request the chapter does not support (below 800 kcal, age 65+ deficit, BMI under 25 for loss, 25 or more for gain, pregnancy)"
          },
          "401": {
            "description": "Missing or invalid API key"
          },
          "429": {
            "description": "Rate limited"
          }
        }
      }
    },
    "/weight-management/loss-plan": {
      "post": {
        "tags": [
          "Weight Management"
        ],
        "summary": "Weight-loss plan: intake, expected rate, goal weights",
        "description": "Maintenance energy from POST /energy/requirements (or maintenance_kcal), minus a 500-1000 kcal deficit (or a chosen intake). Returns the chapter's rate band by BMI beside the static expectation (deficit x 7 / 3500), 5% and 10% goal weights with timelines, protein at 1.2 g/kg, a macro split checked against the AMDR, supplement flag (below 1200 kcal women, 1800 kcal men), maintenance energy at the goal weight and treatment options. Refuses: intake below 800 kcal (VLCD is medically supervised), BMI under 25, under 19 years, pregnancy or lactation. From age 65 only maintenance is returned.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "sex": {
                    "type": "string",
                    "enum": [
                      "male",
                      "female"
                    ]
                  },
                  "age_years": {
                    "type": "number"
                  },
                  "weight_kg": {
                    "type": "number"
                  },
                  "height_cm": {
                    "type": "number"
                  },
                  "pal_category": {
                    "type": "string",
                    "enum": [
                      "sedentary",
                      "low_active",
                      "active",
                      "very_active"
                    ]
                  },
                  "pal": {
                    "type": "number"
                  },
                  "activities": {
                    "type": "array",
                    "items": {
                      "type": "object"
                    }
                  },
                  "activity_log": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "activity": {
                          "type": "string"
                        },
                        "type": {
                          "type": "string"
                        },
                        "minutes": {
                          "type": "number"
                        },
                        "hours": {
                          "type": "number"
                        }
                      }
                    }
                  },
                  "activity_factor": {
                    "type": "string",
                    "enum": [
                      "minimal",
                      "moderate",
                      "strenuous"
                    ]
                  },
                  "maintenance_kcal": {
                    "type": "number",
                    "description": "Replaces the energy engine estimate (800-8000)"
                  },
                  "deficit_kcal": {
                    "type": "number",
                    "description": "500-1000, default 500"
                  },
                  "intake_kcal": {
                    "type": "number",
                    "description": "Alternative to deficit_kcal, 800 or more"
                  },
                  "target_weight_kg": {
                    "type": "number"
                  },
                  "comorbidity_count": {
                    "type": "number"
                  }
                },
                "required": [
                  "sex",
                  "age_years",
                  "weight_kg",
                  "height_cm"
                ]
              },
              "example": {
                "sex": "female",
                "age_years": 35,
                "weight_kg": 81.8,
                "height_cm": 165.1,
                "pal_category": "sedentary",
                "target_weight_kg": 72
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Result with steps, warnings and sources"
          },
          "400": {
            "description": "Invalid input, or a request the chapter does not support (below 800 kcal, age 65+ deficit, BMI under 25 for loss, 25 or more for gain, pregnancy)"
          },
          "401": {
            "description": "Missing or invalid API key"
          },
          "429": {
            "description": "Rate limited"
          }
        }
      }
    },
    "/weight-management/gain-plan": {
      "post": {
        "tags": [
          "Weight Management"
        ],
        "summary": "Weight-gain plan for underweight adults",
        "description": "Maintenance energy (POST /energy/requirements, or maintenance_kcal) plus 500-1000 kcal a day (the chapter's example: 2400 maintains, 2900-3400 gains), protein 12-15% and fat about 30% of energy, expected rate (derived from the 3500 kcal/lb rule), the target weight range (BMI 18.5-24.9, or 23-24.9 in older adults) and the Table 21.12 foods for the +500 and +1000 tiers. Below BMI 18.5 no IOM equation applies, so send activity_factor or maintenance_kcal. Refuses BMI 25 or more and ages under 19.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "sex": {
                    "type": "string",
                    "enum": [
                      "male",
                      "female"
                    ]
                  },
                  "age_years": {
                    "type": "number"
                  },
                  "weight_kg": {
                    "type": "number"
                  },
                  "height_cm": {
                    "type": "number"
                  },
                  "pal_category": {
                    "type": "string",
                    "enum": [
                      "sedentary",
                      "low_active",
                      "active",
                      "very_active"
                    ]
                  },
                  "pal": {
                    "type": "number"
                  },
                  "activities": {
                    "type": "array",
                    "items": {
                      "type": "object"
                    }
                  },
                  "activity_log": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "activity": {
                          "type": "string"
                        },
                        "type": {
                          "type": "string"
                        },
                        "minutes": {
                          "type": "number"
                        },
                        "hours": {
                          "type": "number"
                        }
                      }
                    }
                  },
                  "activity_factor": {
                    "type": "string",
                    "enum": [
                      "minimal",
                      "moderate",
                      "strenuous"
                    ]
                  },
                  "maintenance_kcal": {
                    "type": "number",
                    "description": "Replaces the energy engine estimate (800-8000)"
                  },
                  "surplus_kcal": {
                    "type": "number",
                    "description": "500-1000; omit for the whole range"
                  },
                  "target_weight_kg": {
                    "type": "number"
                  }
                },
                "required": [
                  "sex",
                  "age_years",
                  "weight_kg",
                  "height_cm"
                ]
              },
              "example": {
                "sex": "male",
                "age_years": 30,
                "weight_kg": 50,
                "height_cm": 175,
                "maintenance_kcal": 2400
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Result with steps, warnings and sources"
          },
          "400": {
            "description": "Invalid input, or a request the chapter does not support (below 800 kcal, age 65+ deficit, BMI under 25 for loss, 25 or more for gain, pregnancy)"
          },
          "401": {
            "description": "Missing or invalid API key"
          },
          "429": {
            "description": "Rate limited"
          }
        }
      }
    },
    "/weight-management/progress": {
      "post": {
        "tags": [
          "Weight Management"
        ],
        "summary": "Track progress from weigh-ins",
        "description": "Stateless: send the weigh-ins. Returns total and recent rate, whether the rate is inside the guideline band, 5% and 10% milestones, a plateau flag (derived from the lowest guideline rate), target progress, a National Weight Control Registry check for maintenance, and Blackburn significance when weight falls while the goal is not loss.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "goal": {
                    "type": "string",
                    "enum": [
                      "loss",
                      "gain",
                      "maintain"
                    ]
                  },
                  "weigh_ins": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "date": {
                          "type": "string",
                          "format": "date"
                        },
                        "weight_kg": {
                          "type": "number"
                        }
                      }
                    }
                  },
                  "height_cm": {
                    "type": "number"
                  },
                  "start_weight_kg": {
                    "type": "number"
                  },
                  "target_weight_kg": {
                    "type": "number"
                  },
                  "recent_days": {
                    "type": "number",
                    "description": "14-90, default 28"
                  },
                  "weight_before_loss_kg": {
                    "type": "number",
                    "description": "Maintain goal"
                  }
                },
                "required": [
                  "goal",
                  "weigh_ins"
                ]
              },
              "example": {
                "goal": "loss",
                "height_cm": 170,
                "weigh_ins": [
                  {
                    "date": "2026-01-01",
                    "weight_kg": 100
                  },
                  {
                    "date": "2026-02-05",
                    "weight_kg": 97.5
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Result with steps, warnings and sources"
          },
          "400": {
            "description": "Invalid input, or a request the chapter does not support (below 800 kcal, age 65+ deficit, BMI under 25 for loss, 25 or more for gain, pregnancy)"
          },
          "401": {
            "description": "Missing or invalid API key"
          },
          "429": {
            "description": "Rate limited"
          }
        }
      }
    },
    "/weight-management/diet-pattern": {
      "post": {
        "tags": [
          "Weight Management"
        ],
        "summary": "Classify a daily macronutrient pattern",
        "description": "From daily grams: energy, comparison with the AMDR (the chapter's own ranges for children), pattern tags (very low fat, low fat below AMDR, ketogenic under 50 g carbohydrate, low carbohydrate, high-protein low-carbohydrate), nutrients at risk and metabolic effects from Table 21.6, alcohol share of energy and protein g/kg (1.2 g/kg during energy restriction, 0.8-1.5 for a VLCD).",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "carbohydrate_g": {
                    "type": "number"
                  },
                  "protein_g": {
                    "type": "number"
                  },
                  "fat_g": {
                    "type": "number"
                  },
                  "alcohol_g": {
                    "type": "number"
                  },
                  "weight_kg": {
                    "type": "number"
                  },
                  "age_years": {
                    "type": "number"
                  },
                  "energy_restriction": {
                    "type": "boolean"
                  },
                  "vlcd": {
                    "type": "boolean"
                  }
                }
              },
              "example": {
                "carbohydrate_g": 30,
                "protein_g": 100,
                "fat_g": 140
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Result with steps, warnings and sources"
          },
          "400": {
            "description": "Invalid input, or a request the chapter does not support (below 800 kcal, age 65+ deficit, BMI under 25 for loss, 25 or more for gain, pregnancy)"
          },
          "401": {
            "description": "Missing or invalid API key"
          },
          "429": {
            "description": "Rate limited"
          }
        }
      }
    },
    "/pregnancy/reference": {
      "get": {
        "tags": [
          "Pregnancy"
        ],
        "summary": "Pregnancy and lactation reference tables",
        "description": "Krause chapter 14 tables: IOM weight gain (Table 14.11), energy add-ons, protein, twins (Table 14.12), lab ranges (14.4), anemia cut-points with altitude and smoking (14.5), GDM glucose criteria (14.14), preeclampsia criteria (Box 14.4), adolescent risk factors (Box 14.3), lactation, and the places the chapter contradicts itself. Optional section filter.",
        "parameters": [
          {
            "name": "section",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "weight_gain",
                "energy",
                "protein",
                "twins",
                "lab_ranges",
                "anemia",
                "gdm",
                "preeclampsia",
                "nutrients",
                "adolescent",
                "lactation",
                "known_issues"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Reference"
          },
          "400": {
            "description": "Unknown section"
          },
          "401": {
            "description": "Missing or invalid API key"
          }
        }
      }
    },
    "/pregnancy/gestational-age": {
      "post": {
        "tags": [
          "Pregnancy"
        ],
        "summary": "Gestational age, trimester and due date",
        "description": "From the last menstrual period, a due date, or weeks (and days) as at a reference date (default today, UTC). Returns weeks and days, trimester, due date and chapter-anchored milestones (protein phase, GDM screening window 24-28 weeks, preeclampsia criteria after 20 weeks). The chapter gives no dating rules: 280 days from the LMP, and trimesters at under 14, 14 to under 28, and 28 weeks or more, are conventions and are stated as such.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "lmp": {
                    "type": "string",
                    "format": "date",
                    "description": "Last menstrual period"
                  },
                  "edd": {
                    "type": "string",
                    "format": "date",
                    "description": "Due date"
                  },
                  "ga_weeks": {
                    "type": "number",
                    "description": "Whole weeks, as at reference_date"
                  },
                  "ga_days": {
                    "type": "number",
                    "description": "0-6"
                  },
                  "reference_date": {
                    "type": "string",
                    "format": "date",
                    "description": "Default: today (UTC)"
                  }
                }
              },
              "example": {
                "lmp": "2026-04-01"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Result with steps, warnings and sources"
          },
          "400": {
            "description": "Invalid input"
          },
          "401": {
            "description": "Missing or invalid API key"
          },
          "429": {
            "description": "Rate limited"
          }
        }
      }
    },
    "/pregnancy/weight-gain": {
      "post": {
        "tags": [
          "Pregnancy"
        ],
        "summary": "Pre-pregnancy BMI category and gestational weight gain",
        "description": "IOM totals and weekly rates by pre-pregnancy BMI (Table 14.11, Appendix 3), first-trimester allowance (1-3 kg, 0.5-2 kg if obese), twin totals (provisional; none for underweight), notes for triplets and quadruplets. With current_weight_kg and a gestational age, returns below/within/above against an approximate expected range (a straight line from the first-trimester allowance at 13 weeks to the printed total at 40 weeks). weigh_ins gives a series and the recent weekly rate. Pregnant adolescents get the Box 14.3 risk factors.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "pre_pregnancy_weight_kg": {
                    "type": "number"
                  },
                  "height_cm": {
                    "type": "number"
                  },
                  "prepregnancy_bmi": {
                    "type": "number",
                    "description": "Instead of weight and height"
                  },
                  "multiples": {
                    "type": "number",
                    "description": "1-4, default 1"
                  },
                  "age_years": {
                    "type": "number"
                  },
                  "current_weight_kg": {
                    "type": "number"
                  },
                  "lmp": {
                    "type": "string",
                    "format": "date",
                    "description": "Last menstrual period"
                  },
                  "edd": {
                    "type": "string",
                    "format": "date",
                    "description": "Due date"
                  },
                  "ga_weeks": {
                    "type": "number",
                    "description": "Whole weeks, as at reference_date"
                  },
                  "ga_days": {
                    "type": "number",
                    "description": "0-6"
                  },
                  "reference_date": {
                    "type": "string",
                    "format": "date",
                    "description": "Default: today (UTC)"
                  },
                  "weigh_ins": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "date": {
                          "type": "string",
                          "format": "date"
                        },
                        "weight_kg": {
                          "type": "number"
                        }
                      }
                    }
                  }
                }
              },
              "example": {
                "pre_pregnancy_weight_kg": 60,
                "height_cm": 165,
                "current_weight_kg": 66,
                "lmp": "2026-04-01"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Result with steps, warnings and sources"
          },
          "400": {
            "description": "Invalid input"
          },
          "401": {
            "description": "Missing or invalid API key"
          },
          "429": {
            "description": "Rate limited"
          }
        }
      }
    },
    "/pregnancy/plan": {
      "post": {
        "tags": [
          "Pregnancy"
        ],
        "summary": "Pregnancy or lactation nutrition plan",
        "description": "Pregnant: works the trimester out from gestational age and takes energy from POST /energy/requirements (add-ons 0, +340, +452 kcal), protein at 0.8 g/kg before 20 weeks and 1.1 g/kg after (on current weight), DRI pregnancy targets with percent of the non-pregnant value (GET /dri), nutrient notes, the weight-gain goal, and for twins the Table 14.12 targets. Lactating: +330 kcal (0-6 months) or +400 (7-12), the 1800 kcal minimum, +25 g protein and the weight-loss rule. No deficit advice.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "status": {
                    "type": "string",
                    "enum": [
                      "pregnant",
                      "lactating"
                    ]
                  },
                  "age_years": {
                    "type": "number",
                    "description": "14-50"
                  },
                  "pre_pregnancy_weight_kg": {
                    "type": "number"
                  },
                  "height_cm": {
                    "type": "number"
                  },
                  "pal_category": {
                    "type": "string",
                    "enum": [
                      "sedentary",
                      "low_active",
                      "active",
                      "very_active"
                    ]
                  },
                  "pal": {
                    "type": "number"
                  },
                  "activities": {
                    "type": "array",
                    "items": {
                      "type": "object"
                    }
                  },
                  "activity_factor": {
                    "type": "string",
                    "enum": [
                      "minimal",
                      "moderate",
                      "strenuous"
                    ]
                  },
                  "maintenance_kcal": {
                    "type": "number",
                    "description": "Pre-pregnancy requirement; replaces the engine estimate"
                  },
                  "lmp": {
                    "type": "string",
                    "format": "date",
                    "description": "Last menstrual period"
                  },
                  "edd": {
                    "type": "string",
                    "format": "date",
                    "description": "Due date"
                  },
                  "ga_weeks": {
                    "type": "number",
                    "description": "Whole weeks, as at reference_date"
                  },
                  "ga_days": {
                    "type": "number",
                    "description": "0-6"
                  },
                  "reference_date": {
                    "type": "string",
                    "format": "date",
                    "description": "Default: today (UTC)"
                  },
                  "current_weight_kg": {
                    "type": "number"
                  },
                  "multiples": {
                    "type": "number",
                    "description": "1-4"
                  },
                  "months_postpartum": {
                    "type": "number",
                    "description": "Lactating: 0-24"
                  }
                },
                "required": [
                  "status",
                  "age_years",
                  "pre_pregnancy_weight_kg",
                  "height_cm"
                ]
              },
              "example": {
                "status": "pregnant",
                "age_years": 28,
                "pre_pregnancy_weight_kg": 60,
                "height_cm": 162,
                "pal_category": "low_active",
                "lmp": "2026-04-01",
                "current_weight_kg": 66
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Result with steps, warnings and sources"
          },
          "400": {
            "description": "Invalid input"
          },
          "401": {
            "description": "Missing or invalid API key"
          },
          "429": {
            "description": "Rate limited"
          }
        }
      }
    },
    "/pregnancy/assess": {
      "post": {
        "tags": [
          "Pregnancy"
        ],
        "summary": "Pregnancy lab and clinical screens",
        "description": "Sections, each independent (a bad value only errors its own): anemia from hemoglobin or hematocrit by trimester with altitude and smoking adjustments (Table 14.5); Table 14.4 lab ranges by trimester; GDM glucose tests (50 g screen at 130/135/140, 100 g Carpenter-Coustan and NDDG, 75 g IADPSG and WHO, random glucose); preeclampsia blood pressure, proteinuria and organ-feature criteria (Box 14.4). Screening aids, not diagnoses.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "lmp": {
                    "type": "string",
                    "format": "date",
                    "description": "Last menstrual period"
                  },
                  "edd": {
                    "type": "string",
                    "format": "date",
                    "description": "Due date"
                  },
                  "ga_weeks": {
                    "type": "number",
                    "description": "Whole weeks, as at reference_date"
                  },
                  "ga_days": {
                    "type": "number",
                    "description": "0-6"
                  },
                  "reference_date": {
                    "type": "string",
                    "format": "date",
                    "description": "Default: today (UTC)"
                  },
                  "trimester": {
                    "type": "number",
                    "description": "1-3, instead of a gestational age"
                  },
                  "hemoglobin_g_dl": {
                    "type": "number"
                  },
                  "hematocrit_percent": {
                    "type": "number"
                  },
                  "altitude_m": {
                    "type": "number"
                  },
                  "smoker": {
                    "type": "boolean"
                  },
                  "cigarettes_per_day": {
                    "type": "number"
                  },
                  "labs": {
                    "type": "object",
                    "description": "for example { ferritin_ng_ml: 9, albumin_g_dl: 3.0 }"
                  },
                  "glucose": {
                    "type": "object",
                    "description": "{ test: screen_50g | ogtt_100g | ogtt_75g | random, fasting_mg_dl, one_hour_mg_dl, two_hour_mg_dl, three_hour_mg_dl, random_mg_dl, symptoms }"
                  },
                  "blood_pressure": {
                    "type": "object",
                    "description": "{ systolic_mmhg, diastolic_mmhg, bp_confirmed, urine_protein_24h_mg, protein_creatinine_ratio, dipstick_plus, previously_normal, pulmonary_edema, cerebral_or_visual_symptoms, labs: { platelets_per_ul, creatinine_mg_dl } }"
                  }
                }
              },
              "example": {
                "lmp": "2026-04-01",
                "hemoglobin_g_dl": 10.4,
                "glucose": {
                  "test": "screen_50g",
                  "one_hour_mg_dl": 142
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Result with steps, warnings and sources"
          },
          "400": {
            "description": "Invalid input"
          },
          "401": {
            "description": "Missing or invalid API key"
          },
          "429": {
            "description": "Rate limited"
          }
        }
      }
    },
    "/renal-nutrition/reference": {
      "get": {
        "tags": [
          "Renal Nutrition"
        ],
        "summary": "Renal nutrition reference tables",
        "description": "KDOQI 2020 statements with grades, the older Krause Table 35.9 as legacy figures, Table 35.10 blood-value ranges, adequacy targets, the body-weight adjustment rules, monitoring, CKD stage categories, Box 35.3 and the places the two sources disagree. Optional section filter.",
        "parameters": [
          {
            "name": "section",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "kdoqi_2020",
                "legacy_krause",
                "labs",
                "adequacy",
                "weight_adjustment",
                "monitoring",
                "ckd_stages",
                "emergency_diet",
                "known_issues"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Result"
          },
          "400": {
            "description": "Unknown section"
          },
          "401": {
            "description": "Missing or invalid API key"
          }
        }
      }
    },
    "/renal-nutrition/emergency-diet": {
      "get": {
        "tags": [
          "Renal Nutrition"
        ],
        "summary": "Emergency dialysis diet plan",
        "description": "Box 35.3: for 5 days or less when dialysis is not possible. Choices per day by food group, the emergency kit and storage tips. The foods are US products and units; the source gives no local equivalents.",
        "responses": {
          "200": {
            "description": "Result"
          },
          "401": {
            "description": "Missing or invalid API key"
          }
        }
      }
    },
    "/renal-nutrition/stage": {
      "post": {
        "tags": [
          "Renal Nutrition"
        ],
        "summary": "eGFR and CKD stage",
        "description": "CKD-EPI 2021 creatinine equation (race-free) and KDIGO G categories, with an optional A category from the urine albumin-creatinine ratio. Returns the status and ckd_stage to send to /renal-nutrition/requirements. These equations are not in the KDOQI or Krause documents. Adults only; a single value is a screen, not a diagnosis.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "sex": {
                    "type": "string",
                    "description": "male or female"
                  },
                  "age_years": {
                    "type": "number",
                    "description": "18 or more"
                  },
                  "creatinine_mg_dl": {
                    "type": "number",
                    "description": "Serum creatinine, mg/dL"
                  },
                  "creatinine_umol_l": {
                    "type": "number",
                    "description": "Serum creatinine, umol/L (used if mg/dL is not sent)"
                  },
                  "acr_mg_g": {
                    "type": "number",
                    "description": "Urine albumin-creatinine ratio, mg/g"
                  }
                }
              },
              "example": {
                "sex": "male",
                "age_years": 67,
                "creatinine_mg_dl": 3.3
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Result with steps, warnings and sources"
          },
          "400": {
            "description": "Invalid input"
          },
          "401": {
            "description": "Missing or invalid API key"
          },
          "429": {
            "description": "Rate limited"
          }
        }
      }
    },
    "/renal-nutrition/requirements": {
      "post": {
        "tags": [
          "Renal Nutrition"
        ],
        "summary": "Nutrient targets by CKD status",
        "description": "KDOQI 2020 targets for metabolically stable adults: protein (0.55-0.60 g/kg in CKD 3-5, 0.6-0.8 with diabetes, 1.0-1.2 on dialysis), energy 25-35 kcal/kg, sodium under 2.3 g/day, calcium 800-1000 mg in CKD 3-4, bicarbonate 24-26 mmol/L, and the potassium and phosphorus rules (adjust to serum, no numeric intake target). Amounts per day use the actual weight when it is 95-115% of Hamwi ideal weight, otherwise the adjusted weight. The older Krause Table 35.9 figures come back separately as legacy_krause. Gaps in the 2020 guideline are returned as null with the reason.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "status": {
                    "type": "string",
                    "description": "ckd_1_2, ckd_3_5, hd, pd or transplant"
                  },
                  "weight_kg": {
                    "type": "number",
                    "description": "Actual weight"
                  },
                  "edema_free_weight_kg": {
                    "type": "number",
                    "description": "Used instead of weight_kg when sent"
                  },
                  "height_cm": {
                    "type": "number",
                    "description": "With sex, gives ideal body weight (Hamwi)"
                  },
                  "sex": {
                    "type": "string",
                    "description": "male or female"
                  },
                  "frame": {
                    "type": "string",
                    "description": "small, medium or large (Hamwi -10% or +10%)"
                  },
                  "diabetes": {
                    "type": "boolean",
                    "description": "Changes the CKD 3-5 and dialysis protein statement"
                  },
                  "ckd_stage": {
                    "type": "number",
                    "description": "3-5; the calcium statement covers stages 3-4 only"
                  },
                  "weeks_post_transplant": {
                    "type": "number",
                    "description": "Picks the legacy Krause transplant row"
                  },
                  "weight_basis": {
                    "type": "string",
                    "description": "auto (default), actual, ibw or adjusted"
                  },
                  "metabolically_stable": {
                    "type": "boolean",
                    "description": "Default true; false adds a warning"
                  }
                }
              },
              "example": {
                "status": "hd",
                "weight_kg": 60,
                "height_cm": 165,
                "sex": "female"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Result with steps, warnings and sources"
          },
          "400": {
            "description": "Invalid input"
          },
          "401": {
            "description": "Missing or invalid API key"
          },
          "429": {
            "description": "Rate limited"
          }
        }
      }
    },
    "/renal-nutrition/labs": {
      "post": {
        "tags": [
          "Renal Nutrition"
        ],
        "summary": "Blood values with diet actions",
        "description": "Krause Table 35.10: each value is flagged low, in range or high against the dialysis column (default) or the normal-kidney column, with the diet action. Accepts mg/dL, mEq/L and SI units (umol/L, mmol/L, g/L). Also the calcium x phosphorus product (limit 59). Where KDOQI 2020 differs (phosphate, bicarbonate) the row carries kdoqi_2020_target. Albumin, prealbumin and nPCR are not read alone.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "labs": {
                    "type": "object",
                    "description": "Keys such as sodium_meq_l, potassium_meq_l, bun_mg_dl or urea_mmol_l, creatinine_mg_dl or creatinine_umol_l, albumin_g_dl or albumin_g_l, calcium_mg_dl or calcium_mmol_l, phosphorus_mg_dl or phosphate_mmol_l, pth_pg_ml, aluminum_mcg_l, magnesium_mg_dl, ferritin_mcg_l, co2_meq_l, glucose_mg_dl or glucose_mmol_l"
                  },
                  "context": {
                    "type": "string",
                    "description": "dialysis (default) or general"
                  },
                  "diabetes": {
                    "type": "boolean",
                    "description": "Changes the dialysis glucose limit"
                  },
                  "on_epo": {
                    "type": "boolean",
                    "description": "Changes the dialysis ferritin range (default true)"
                  },
                  "sex": {
                    "type": "string",
                    "description": "Used for the female ferritin range in the general context"
                  }
                }
              },
              "example": {
                "labs": {
                  "potassium_meq_l": 6.1,
                  "phosphorus_mg_dl": 6.7,
                  "calcium_mg_dl": 9.0
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Result with steps, warnings and sources"
          },
          "400": {
            "description": "Invalid input"
          },
          "401": {
            "description": "Missing or invalid API key"
          },
          "429": {
            "description": "Rate limited"
          }
        }
      }
    },
    "/renal-nutrition/adequacy": {
      "post": {
        "tags": [
          "Renal Nutrition"
        ],
        "summary": "Dialysis adequacy",
        "description": "URR from pre and post BUN or urea (65% or more), Kt/V and eKt/V against 1.2, weekly Kt/V of 2 on peritoneal dialysis, nPNA (nPCR) against 0.8-1.4 g/kg/day, and interdialytic weight gain against 4% of dry weight. With session_hours, uf_litres and post_weight_kg it also computes the Daugirdas second-generation spKt/V and rate-adjusted eKt/V, which are not in the supplied sources.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "modality": {
                    "type": "string",
                    "description": "hd or pd"
                  },
                  "pre_bun_mg_dl": {
                    "type": "number",
                    "description": "Pre-dialysis BUN"
                  },
                  "post_bun_mg_dl": {
                    "type": "number",
                    "description": "Post-dialysis BUN"
                  },
                  "pre_urea_mmol_l": {
                    "type": "number",
                    "description": "Pre-dialysis urea, used if BUN is not sent"
                  },
                  "post_urea_mmol_l": {
                    "type": "number",
                    "description": "Post-dialysis urea"
                  },
                  "session_hours": {
                    "type": "number",
                    "description": "Session length"
                  },
                  "uf_litres": {
                    "type": "number",
                    "description": "Ultrafiltration volume"
                  },
                  "post_weight_kg": {
                    "type": "number",
                    "description": "Post-dialysis weight"
                  },
                  "kt_v": {
                    "type": "number",
                    "description": "Reported Kt/V (hemodialysis)"
                  },
                  "ekt_v": {
                    "type": "number",
                    "description": "Reported eKt/V"
                  },
                  "weekly_kt_v": {
                    "type": "number",
                    "description": "Peritoneal dialysis"
                  },
                  "npna": {
                    "type": "number",
                    "description": "Also accepted as npcr"
                  },
                  "pre_dialysis_weight_kg": {
                    "type": "number",
                    "description": "With dry_weight_kg, gives interdialytic gain"
                  },
                  "dry_weight_kg": {
                    "type": "number",
                    "description": "Previous post-dialysis weight"
                  }
                }
              },
              "example": {
                "modality": "hd",
                "pre_bun_mg_dl": 60,
                "post_bun_mg_dl": 20,
                "session_hours": 4,
                "uf_litres": 2.5,
                "post_weight_kg": 70
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Result with steps, warnings and sources"
          },
          "400": {
            "description": "Invalid input"
          },
          "401": {
            "description": "Missing or invalid API key"
          },
          "429": {
            "description": "Rate limited"
          }
        }
      }
    },
    "/diabetes/reference": {
      "get": {
        "tags": [
          "Diabetes Nutrition"
        ],
        "summary": "Diabetes nutrition reference tables",
        "description": "Krause chapter 30, ADA Standards of Care 2026 and UCT tables. section = diagnosis, a1c_eag, glycemic_targets, pregnancy_targets, hypoglycemia, insulin_rules, insulin_action_times, exercise, sick_day, nutrition_recommendations, food_list_values, medication_nutrition or known_issues. Without a section, lists them.",
        "parameters": [
          {
            "name": "section",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Reference table"
          },
          "400": {
            "description": "Unknown section"
          },
          "401": {
            "description": "Missing or invalid API key"
          }
        }
      }
    },
    "/diabetes/status": {
      "post": {
        "tags": [
          "Diabetes Nutrition"
        ],
        "summary": "Diagnosis category and estimated average glucose",
        "description": "A1C, fasting, 2-hour OGTT or random glucose (mg/dL or mmol/L) to normal, prediabetes or diabetes per Krause Table 30.1, plus A1C to eAG and mmol/mol.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              },
              "example": {
                "a1c_percent": 6.8,
                "fpg_mg_dl": 130
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Result with sources and disclaimer"
          },
          "400": {
            "description": "Invalid input"
          },
          "401": {
            "description": "Missing or invalid API key"
          },
          "429": {
            "description": "Rate limited"
          }
        }
      }
    },
    "/diabetes/glucose-targets": {
      "post": {
        "tags": [
          "Diabetes Nutrition"
        ],
        "summary": "Glucose targets",
        "description": "population nonpregnant_adult (ADA 2026) or pregnancy.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              },
              "example": {
                "population": "nonpregnant_adult"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Result with sources and disclaimer"
          },
          "400": {
            "description": "Invalid input"
          },
          "401": {
            "description": "Missing or invalid API key"
          },
          "429": {
            "description": "Rate limited"
          }
        }
      }
    },
    "/diabetes/carb-plan": {
      "post": {
        "tags": [
          "Diabetes Nutrition"
        ],
        "summary": "Carbohydrate prescription and distribution",
        "description": "Daily carbohydrate from carb_g_per_day, energy_kcal with carb_pct, or the Krause starting servings by sex, split across meals and snacks in grams and 15 g exchanges. population gdm applies the pregnancy pattern.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              },
              "example": {
                "energy_kcal": 1900,
                "carb_pct": 50,
                "meals": 3,
                "snacks": 3
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Result with sources and disclaimer"
          },
          "400": {
            "description": "Invalid input"
          },
          "401": {
            "description": "Missing or invalid API key"
          },
          "429": {
            "description": "Rate limited"
          }
        }
      }
    },
    "/diabetes/carb-count": {
      "post": {
        "tags": [
          "Diabetes Nutrition"
        ],
        "summary": "Carbohydrate counting from a food label",
        "description": "Subtracts half of fibre and sugar alcohol only when together they exceed 5 g per serving (Krause p.637).",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              },
              "example": {
                "total_carb_g": 30,
                "fibre_g": 8
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Result with sources and disclaimer"
          },
          "400": {
            "description": "Invalid input"
          },
          "401": {
            "description": "Missing or invalid API key"
          },
          "429": {
            "description": "Rate limited"
          }
        }
      }
    },
    "/diabetes/exchange-plan": {
      "post": {
        "tags": [
          "Diabetes Nutrition"
        ],
        "summary": "Carbohydrate targets linked to the exchange list",
        "description": "Reads exchange_lists (read only). exchanges, meals or carb_plan; otherwise returns exchange types and items.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              },
              "example": {
                "exchanges": {
                  "starch": 10,
                  "fruit": 3,
                  "milk": 2
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Result with sources and disclaimer"
          },
          "400": {
            "description": "Invalid input"
          },
          "401": {
            "description": "Missing or invalid API key"
          },
          "429": {
            "description": "Rate limited"
          }
        }
      }
    },
    "/diabetes/glycemic-load": {
      "post": {
        "tags": [
          "Diabetes Nutrition"
        ],
        "summary": "Glycaemic load of foods and a meal",
        "description": "Reads glycaemic_index_data (read only). GL = GI x available carbohydrate / 100.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              },
              "example": {
                "foods": [
                  {
                    "food": "nsima",
                    "carbs_g": 50
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Result with sources and disclaimer"
          },
          "400": {
            "description": "Invalid input"
          },
          "401": {
            "description": "Missing or invalid API key"
          },
          "429": {
            "description": "Rate limited"
          }
        }
      }
    },
    "/diabetes/insulin": {
      "post": {
        "tags": [
          "Diabetes Nutrition"
        ],
        "summary": "Insulin-to-carbohydrate ratio, correction factor and dose",
        "description": "500 and 1700 rules from total daily dose (Krause) or clinician-set ratios. A starting estimate for the prescriber. No dose below 70 mg/dL.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              },
              "example": {
                "tdd_units": 50,
                "carbs_g": 60,
                "current_glucose_mg_dl": 204,
                "target_glucose_mg_dl": 100
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Result with sources and disclaimer"
          },
          "400": {
            "description": "Invalid input"
          },
          "401": {
            "description": "Missing or invalid API key"
          },
          "429": {
            "description": "Rate limited"
          }
        }
      }
    },
    "/diabetes/hypoglycemia": {
      "post": {
        "tags": [
          "Diabetes Nutrition"
        ],
        "summary": "Hypoglycaemia level and treatment steps",
        "description": "ADA 2026 levels 1-3 and treatment.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              },
              "example": {
                "glucose_mg_dl": 60
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Result with sources and disclaimer"
          },
          "400": {
            "description": "Invalid input"
          },
          "401": {
            "description": "Missing or invalid API key"
          },
          "429": {
            "description": "Rate limited"
          }
        }
      }
    },
    "/diabetes/exercise": {
      "post": {
        "tags": [
          "Diabetes Nutrition"
        ],
        "summary": "Extra carbohydrate for exercise",
        "description": "15 g per 30-60 min of activity on insulin or a secretagogue (Krause p.639).",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              },
              "example": {
                "duration_min": 60,
                "on_insulin_or_secretagogue": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Result with sources and disclaimer"
          },
          "400": {
            "description": "Invalid input"
          },
          "401": {
            "description": "Missing or invalid API key"
          },
          "429": {
            "description": "Rate limited"
          }
        }
      }
    },
    "/diabetes/plan": {
      "post": {
        "tags": [
          "Diabetes Nutrition"
        ],
        "summary": "Glycaemic status, targets and carbohydrate plan",
        "description": "labs, population and carb in one call.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              },
              "example": {
                "labs": {
                  "a1c_percent": 8.1
                },
                "carb": {
                  "energy_kcal": 1800,
                  "carb_pct": 45
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Result with sources and disclaimer"
          },
          "400": {
            "description": "Invalid input"
          },
          "401": {
            "description": "Missing or invalid API key"
          },
          "429": {
            "description": "Rate limited"
          }
        }
      }
    },
    "/renal-nutrition/food-plan": {
      "post": {
        "tags": [
          "Renal Nutrition"
        ],
        "summary": "Renal targets linked to the food exchange list",
        "description": "Runs /renal-nutrition/requirements, reads exchange_lists and renal_foods (read only) and returns each exchange item as a percentage of the daily protein (KDOQI 2020), sodium (KDOQI 2020), potassium and phosphorus limits, per-group averages with the exchanges that fit before the tightest limit, and the matching renal_foods row. KDOQI 2020 has no potassium or phosphorus intake number, so those budgets need a clinician-set limit or legacy_krause_limits. Column names are detected, not assumed: field_map and missing_fields show what was found.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "status": {
                    "type": "string",
                    "description": "ckd_1_2, ckd_3_5, hd, pd or transplant (same as /requirements)"
                  },
                  "weight_kg": {
                    "type": "number",
                    "description": "Required"
                  },
                  "exchange_type": {
                    "type": "string",
                    "description": "Only items in this exchange group"
                  },
                  "q": {
                    "type": "string",
                    "description": "Only items whose name contains this"
                  },
                  "limit": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 300,
                    "default": 100
                  },
                  "share_flag_pct": {
                    "type": "number",
                    "minimum": 5,
                    "maximum": 100,
                    "default": 25,
                    "description": "An item at or above this percentage of a daily limit is flagged high_share"
                  },
                  "potassium_mg_per_day_max": {
                    "type": "number",
                    "description": "Clinician-set limit"
                  },
                  "phosphorus_mg_per_day_max": {
                    "type": "number",
                    "description": "Clinician-set limit"
                  },
                  "legacy_krause_limits": {
                    "type": "boolean",
                    "description": "Use the older Krause Table 35.9 potassium and phosphorus maxima when no clinician limit is sent"
                  }
                }
              },
              "example": {
                "status": "hd",
                "weight_kg": 60,
                "potassium_mg_per_day_max": 2000,
                "exchange_type": "protein"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Daily budget, field_map, per-group exchanges and items with percentage of daily limit"
          },
          "400": {
            "description": "Invalid input"
          },
          "401": {
            "description": "Missing or invalid API key"
          },
          "429": {
            "description": "Rate limited"
          }
        }
      }
    },
    "/renal-nutrition/plan": {
      "post": {
        "tags": [
          "Renal Nutrition"
        ],
        "summary": "Renal nutrition plan in one call",
        "description": "Runs stage (from creatinine), requirements, labs and adequacy for the parts sent, plus the KDOQI 2020 monitoring schedule (weight monthly on dialysis, every 3 months in CKD 4-5 or after transplant, every 6 months in CKD 1-3; screening twice a year; full assessment within 90 days of starting dialysis; 3-day food record; 7-point SGA on dialysis). A part that fails is reported in errors and does not stop the others. Without status, the status is taken from the eGFR stage.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "status": {
                    "type": "string",
                    "description": "ckd_1_2, ckd_3_5, hd, pd or transplant; optional if creatinine, sex and age_years are sent"
                  },
                  "weight_kg": {
                    "type": "number",
                    "description": "Required"
                  },
                  "labs": {
                    "type": "object",
                    "description": "Same keys as /renal-nutrition/labs"
                  },
                  "adequacy": {
                    "type": "object",
                    "description": "Same keys as /renal-nutrition/adequacy (hd and pd only)"
                  }
                }
              },
              "example": {
                "sex": "male",
                "age_years": 67,
                "creatinine_mg_dl": 3.3,
                "weight_kg": 48,
                "height_cm": 170,
                "diabetes": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Result with steps, warnings and sources"
          },
          "400": {
            "description": "Invalid input"
          },
          "401": {
            "description": "Missing or invalid API key"
          },
          "429": {
            "description": "Rate limited"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "An API key: a consumer key from the developer console (POST /developer/keys), or an admin or reviewer key minted via POST /admin/keys (the root ADMIN_API_KEY also works). Required on every route except GET /, GET /health and the FHIR conformance reads. For /fhir, every operation needs a credential with the scope system/<ResourceType>.<read|write|*> (role defaults: consumer and admin system/*.*, reviewer system/*.read; narrow with fhir_scopes on /admin/keys). Admin routes refuse consumer keys. Only the root key can manage API keys.\n"
      },
      "webhookSecret": {
        "type": "apiKey",
        "in": "header",
        "name": "x-webhook-secret",
        "description": "Shared secret (INGEST_WEBHOOK_SECRET) for the database storage trigger. Only POST /rag/webhook uses it."
      },
      "signupWebhookSecret": {
        "type": "apiKey",
        "in": "header",
        "name": "x-webhook-secret",
        "description": "Shared secret (SIGNUP_WEBHOOK_SECRET) for the auth.users sign-up trigger. Only POST /hooks/signup uses it."
      },
      "loginToken": {
        "type": "http",
        "scheme": "bearer",
        "description": "The access token from a developer login (Supabase Auth), not an API key. Only /developer/keys uses it. The docs site handles this for you on the account page."
      }
    },
    "parameters": {
      "PathId": {
        "name": "id",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string"
        }
      },
      "Search": {
        "name": "search",
        "in": "query",
        "schema": {
          "type": "string"
        }
      },
      "Limit": {
        "name": "limit",
        "in": "query",
        "schema": {
          "type": "integer",
          "default": 50,
          "maximum": 100
        }
      },
      "Offset": {
        "name": "offset",
        "in": "query",
        "schema": {
          "type": "integer",
          "default": 0
        }
      },
      "Cursor": {
        "name": "cursor",
        "in": "query",
        "description": "Switches to keyset pagination when present (even empty). See README § Pagination. Response then has has_more/next_cursor instead of count/offset, and is always ordered by id.asc.\n",
        "schema": {
          "type": "string"
        }
      },
      "UserId": {
        "name": "user_id",
        "in": "query",
        "required": true,
        "description": "Client-supplied identifier - no server-side account system exists.",
        "schema": {
          "type": "string"
        }
      },
      "ResourceType": {
        "name": "resource_type",
        "in": "query",
        "schema": {
          "type": "string",
          "enum": [
            "food",
            "packaged"
          ]
        }
      }
    },
    "responses": {
      "BadRequest": {
        "description": "Invalid request",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Unauthorized": {
        "description": "Missing/invalid API key",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "NotFound": {
        "description": "Resource not found",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "RateLimited": {
        "description": "Rate limit exceeded",
        "headers": {
          "Retry-After": {
            "schema": {
              "type": "integer"
            }
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "MutationSuccess": {
        "description": "Success",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Success"
            }
          }
        }
      },
      "BulkSuccess": {
        "description": "Bulk insert success",
        "content": {
          "application/json": {
            "schema": {
              "allOf": [
                {
                  "$ref": "#/components/schemas/Success"
                },
                {
                  "type": "object",
                  "properties": {
                    "count": {
                      "type": "integer"
                    },
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "additionalProperties": true
                      }
                    }
                  }
                }
              ]
            }
          }
        }
      },
      "ListOrCursor": {
        "description": "Offset/limit list (default) or cursor page (when ?cursor= is present)",
        "content": {
          "application/json": {
            "schema": {
              "oneOf": [
                {
                  "$ref": "#/components/schemas/ListSuccess"
                },
                {
                  "$ref": "#/components/schemas/CursorSuccess"
                }
              ]
            }
          }
        }
      }
    },
    "schemas": {
      "EpidemiologyRule": {
        "type": "object",
        "required": [
          "variable"
        ],
        "description": "A binary rule on one column. Send at most one of positive, below, at_or_below, above, at_or_above, between. With none, the column is read as yes/no, true/false or 1/0.",
        "properties": {
          "variable": {
            "type": "string"
          },
          "positive": {
            "description": "value or list of values that count as positive (case-insensitive)"
          },
          "negative": {
            "description": "optional: values that count as negative; any other value is then excluded as unrecognised"
          },
          "below": {
            "type": "number"
          },
          "at_or_below": {
            "type": "number"
          },
          "above": {
            "type": "number"
          },
          "at_or_above": {
            "type": "number"
          },
          "between": {
            "type": "array",
            "minItems": 2,
            "maxItems": 2,
            "items": {
              "type": "number"
            },
            "description": "[low, high): low included, high not"
          },
          "name": {
            "type": "string",
            "description": "label for an indicator"
          }
        }
      },
      "EpidemiologyTable": {
        "type": "object",
        "required": [
          "a",
          "b",
          "c",
          "d"
        ],
        "properties": {
          "label": {
            "type": "string"
          },
          "a": {
            "type": "integer",
            "minimum": 0,
            "description": "exposed with the outcome"
          },
          "b": {
            "type": "integer",
            "minimum": 0,
            "description": "exposed without the outcome"
          },
          "c": {
            "type": "integer",
            "minimum": 0,
            "description": "unexposed with the outcome"
          },
          "d": {
            "type": "integer",
            "minimum": 0,
            "description": "unexposed without the outcome"
          }
        }
      },
      "Error": {
        "type": "object",
        "properties": {
          "status": {
            "type": "string",
            "example": "error"
          },
          "message": {
            "type": "string"
          },
          "request_id": {
            "type": "string",
            "description": "Present on 500 responses."
          }
        }
      },
      "Success": {
        "type": "object",
        "properties": {
          "status": {
            "type": "string",
            "example": "success"
          },
          "message": {
            "type": "string"
          },
          "data": {
            "oneOf": [
              {
                "type": "object",
                "additionalProperties": true
              },
              {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            ]
          }
        }
      },
      "FhirOperationOutcome": {
        "type": "object",
        "description": "FHIR R4 OperationOutcome - the shape every /fhir/* error response uses in place of the plain { status, message } Error schema.\n",
        "properties": {
          "resourceType": {
            "type": "string",
            "enum": [
              "OperationOutcome"
            ]
          },
          "issue": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "severity": {
                  "type": "string",
                  "enum": [
                    "error",
                    "warning"
                  ]
                },
                "code": {
                  "type": "string",
                  "example": "invalid"
                },
                "diagnostics": {
                  "type": "string"
                }
              }
            }
          }
        }
      },
      "ListSuccess": {
        "type": "object",
        "properties": {
          "status": {
            "type": "string",
            "example": "success"
          },
          "count": {
            "type": "integer"
          },
          "limit": {
            "type": "integer"
          },
          "offset": {
            "type": "integer"
          },
          "data": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": true
            }
          }
        }
      },
      "CursorSuccess": {
        "type": "object",
        "properties": {
          "status": {
            "type": "string",
            "example": "success"
          },
          "limit": {
            "type": "integer"
          },
          "has_more": {
            "type": "boolean"
          },
          "next_cursor": {
            "type": "integer",
            "nullable": true
          },
          "data": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": true
            }
          }
        }
      },
      "BulkRequest": {
        "type": "object",
        "required": [
          "items"
        ],
        "properties": {
          "items": {
            "type": "array",
            "minItems": 1,
            "maxItems": 500,
            "items": {
              "type": "object",
              "additionalProperties": true
            }
          }
        }
      },
      "FavoriteRequest": {
        "type": "object",
        "required": [
          "user_id",
          "resource_type",
          "resource_id"
        ],
        "properties": {
          "user_id": {
            "type": "string",
            "example": "device-abc123"
          },
          "resource_type": {
            "type": "string",
            "enum": [
              "food",
              "packaged"
            ]
          },
          "resource_id": {
            "type": "integer"
          }
        }
      },
      "HealthStatus": {
        "type": "object",
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "healthy",
              "degraded"
            ]
          },
          "version": {
            "type": "string"
          },
          "checked_at": {
            "type": "string",
            "format": "date-time"
          },
          "services": {
            "type": "object",
            "properties": {
              "supabase": {
                "$ref": "#/components/schemas/ServiceCheck"
              },
              "cohere": {
                "$ref": "#/components/schemas/ServiceCheck"
              },
              "groq": {
                "$ref": "#/components/schemas/ServiceCheck"
              },
              "open_food_facts": {
                "$ref": "#/components/schemas/ServiceCheck"
              },
              "usda_fdc": {
                "$ref": "#/components/schemas/ServiceCheck"
              },
              "fatsecret": {
                "$ref": "#/components/schemas/ServiceCheck"
              },
              "rate_limit_kv": {
                "$ref": "#/components/schemas/ServiceCheck"
              }
            }
          }
        }
      },
      "ServiceCheck": {
        "type": "object",
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "ok",
              "error",
              "configured",
              "not_configured",
              "bound",
              "not_bound"
            ]
          },
          "detail": {
            "type": "string"
          }
        }
      },
      "ApiKey": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer"
          },
          "label": {
            "type": "string"
          },
          "role": {
            "type": "string",
            "enum": [
              "admin",
              "reviewer"
            ],
            "default": "admin"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "last_used_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "revoked_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          }
        }
      },
      "Food": {
        "type": "object",
        "required": [
          "food_name"
        ],
        "properties": {
          "id": {
            "type": "integer",
            "readOnly": true
          },
          "food_name": {
            "type": "string",
            "example": "Nsima (thick, maize)"
          },
          "category": {
            "type": "string",
            "example": "Staples"
          },
          "measure": {
            "type": "string",
            "example": "1 cup / chikombe (240g)"
          },
          "weight_g": {
            "type": "number"
          },
          "kcal": {
            "type": "number"
          },
          "kj": {
            "type": "number"
          },
          "protein_g": {
            "type": "number"
          },
          "carbs_g": {
            "type": "number"
          },
          "fat_g": {
            "type": "number"
          }
        },
        "additionalProperties": true
      },
      "PackagedFood": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "readOnly": true
          },
          "product_name": {
            "type": "string"
          },
          "brand": {
            "type": "string",
            "nullable": true
          },
          "barcode": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "approved",
              "rejected"
            ]
          },
          "source": {
            "type": "string",
            "enum": [
              "manual",
              "ocr_ai"
            ],
            "nullable": true
          },
          "ai_confidence": {
            "type": "number",
            "nullable": true
          },
          "submitted_at": {
            "type": "string",
            "format": "date-time"
          },
          "reviewed_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "reviewed_by": {
            "type": "string",
            "nullable": true
          },
          "rejection_reason": {
            "type": "string",
            "nullable": true
          },
          "energy_kcal": {
            "type": "number",
            "nullable": true
          },
          "protein_g": {
            "type": "number",
            "nullable": true
          },
          "fat_g": {
            "type": "number",
            "nullable": true
          },
          "carbs_g": {
            "type": "number",
            "nullable": true
          }
        },
        "additionalProperties": true
      }
    }
  }
}
