{"status":"success","data":{"api_key_required":true,"name":"Chakudya Nutrition Registry (CNR)","tagline":"Malawi's Intelligent Food & Nutrition Knowledge Platform","version":"1.59.0","maintainer":"Edison Taimu","auth":"Every operational route needs 'Authorization: Bearer <API key>': a free consumer key from the developer console, or an admin/reviewer key. Works without a key: GET /, GET /health and the FHIR conformance reads (GET /fhir/metadata, /fhir/CodeSystem/{name}, /fhir/OperationDefinition/{id}, /fhir/StructureDefinition/{name}, /fhir/ConceptMap/{id}, /fhir/Questionnaire). /developer/keys takes your Supabase login token instead of an API key, and POST /rag/webhook takes a shared secret. Registry writes (foods, exchange, renal, formulas, drug-interactions, glycaemic-index and the WHO/Fenton reference tables, packaged CRUD and review, RAG ingest/upload/delete, bulk inserts, key management) additionally need an admin key; the packaged review queue also accepts a reviewer key. POST /packaged/submit, /packaged/scan, /rag/retrieve, /rag/ask, /memory/write, /memory/recall and the other compute and user-data routes need a consumer key (or any admin key), not an admin one.","open_endpoints":["GET /","GET /health","GET /fhir/metadata and the other FHIR conformance reads (CodeSystem, OperationDefinition, StructureDefinition, ConceptMap, Questionnaire)"],"kv_bound":true,"endpoints":{"health":["GET  /health           (no key needed, rate-limited) → pings Supabase/Cohere/Groq/Open Food Facts in parallel, reports per-service status + overall healthy/degraded"],"developer_keys":["POST   /developer/keys       (login token) → body {label}; creates a consumer key (max 3 active per account, email must be confirmed); returns the raw key ONCE","GET    /developer/keys       (login token) → your active keys with last-used time and usage counts","DELETE /developer/keys/:id   (login token) → revoke one of your keys","Send your Supabase login access token as 'Authorization: Bearer <token>' on these three routes only. Consumer keys then go on every other route the same way and get 3x the standard rate limit."],"admin_keys":["POST   /energy/requirements   (API key) → body {sex, age_years|age_months, weight_kg, height_cm, activity_factor?: minimal|moderate|strenuous, pal?|pal_category?|activities?, stress?: [{id: fever|custom, ...}], measured_ree_kcal?, pregnancy?, lactation?} → REE, stress, activity, every applicable IOM equation and one recommended figure with the reason","POST   /energy/food-energy     (API key) → body {protein_g, fat_g, carbohydrate_g, alcohol_g} or {weight_g, protein_pct, fat_pct, carbohydrate_pct} or {alcohol: {volume_oz, proof|abv_percent}} → kcal, kJ, per-nutrient breakdown","GET    /activity-energy        (API key) → ?q=, ?activity=, ?weight_kg= or ?weight_lb= → activity list from the database; with a weight, kcal per hour at that weight","POST   /activity-energy/calculate (API key) → body {weight_kg|weight_lb, activities: [{activity, type?, minutes|hours}]} → kcal per activity and total (Krause 16th ed. Appendix 10)","GET    /pregnancy/reference     (API key) → chapter 14 tables: weight gain (Table 14.11), energy add-ons, protein, twins (14.12), lab ranges (14.4), anemia (14.5), GDM (14.14), preeclampsia (Box 14.4), adolescent risks, lactation, known issues. Optional ?section=","POST   /pregnancy/gestational-age (API key) → body one of {lmp | edd | ga_weeks (+ga_days)} [+reference_date, default today UTC] → weeks+days, trimester, EDD, milestones (dating conventions are not from the chapter)","POST   /pregnancy/weight-gain   (API key) → body {pre_pregnancy_weight_kg + height_cm | prepregnancy_bmi, multiples?, age_years?, current_weight_kg + lmp|edd|ga_weeks?, weigh_ins?: [{date, weight_kg}] + lmp|edd} → category, IOM total and weekly rate, twins, status at the gestational age","POST   /pregnancy/plan          (API key) → body {status: pregnant|lactating, age_years (14-50), pre_pregnancy_weight_kg, height_cm, pal_category|pal|activities|activity_factor | maintenance_kcal, pregnant: lmp|edd|ga_weeks, current_weight_kg?, multiples?; lactating: months_postpartum} → energy from /energy/requirements, protein, DRI targets, twin table, lactation rules","POST   /pregnancy/assess        (API key) → body {lmp|edd|ga_weeks|trimester, hemoglobin_g_dl?, hematocrit_percent?, altitude_m?, smoker?, labs?: {...}, glucose?: {test, ...}, blood_pressure?: {systolic_mmhg, diastolic_mmhg, ...}} → anemia, Table 14.4 labs, GDM test, preeclampsia criteria (each section independent)","GET    /renal-nutrition/reference     (API key) → ?section= kdoqi_2020|legacy_krause|labs|adequacy|weight_adjustment|monitoring|ckd_stages|emergency_diet|known_issues: KDOQI 2020 statements, Krause Tables 35.9 and 35.10, where the two disagree.","GET    /renal-nutrition/emergency-diet (API key) → Box 35.3 emergency dialysis diet: choices per day, kit, storage.","POST   /renal-nutrition/stage         (API key) → body {sex, age_years, creatinine_mg_dl|creatinine_umol_l, acr_mg_g?} → CKD-EPI 2021 eGFR, G and A category, the status to send to /requirements.","POST   /renal-nutrition/requirements  (API key) → body {status: ckd_1_2|ckd_3_5|hd|pd|transplant, weight_kg, height_cm?, sex?, diabetes?, ckd_stage?, edema_free_weight_kg?, weight_basis?} → KDOQI 2020 protein, energy, sodium, calcium, bicarbonate, K and P rules with amounts per day, plus legacy_krause.","POST   /renal-nutrition/labs          (API key) → body {labs: {potassium_meq_l, phosphorus_mg_dl, ...SI keys accepted}, context?: dialysis|general, diabetes?, on_epo?} → low/in range/high with the diet action, Ca x P product.","POST   /renal-nutrition/adequacy      (API key) → body {modality: hd|pd, pre_bun_mg_dl+post_bun_mg_dl, session_hours+uf_litres+post_weight_kg?, ekt_v?, weekly_kt_v?, npna?, pre_dialysis_weight_kg+dry_weight_kg?} → URR, Kt/V, eKt/V, nPNA, interdialytic weight gain against targets.","POST   /renal-nutrition/plan          (API key) → body {status|creatinine+sex+age_years, weight_kg, labs?, adequacy?, ...} → stage, requirements, labs, adequacy and the KDOQI monitoring schedule in one call.","POST   /renal-nutrition/food-plan     (API key) → body = /requirements body + {exchange_type?, q?, limit?, share_flag_pct?, potassium_mg_per_day_max?, phosphorus_mg_per_day_max?, legacy_krause_limits?} → each exchange_lists item as % of the daily protein, sodium, potassium and phosphorus limits, per-group exchanges that fit, and the matching renal_foods row. Read only.","GET    /diabetes/reference           (API key) → ?section= diagnosis|a1c_eag|glycemic_targets|pregnancy_targets|hypoglycemia|insulin_rules|insulin_action_times|exercise|sick_day|nutrition_recommendations|food_list_values|medication_nutrition|known_issues","POST   /diabetes/status              (API key) → body {a1c_percent|a1c_mmol_mol, fpg_mg_dl|fpg_mmol_l, ogtt_2h_mg_dl|ogtt_2h_mmol_l, random_mg_dl|random_mmol_l, classic_symptoms?, a1c_unreliable?} → normal|prediabetes|diabetes (KR Table 30.1) and eAG.","POST   /diabetes/glucose-targets     (API key) → body {population?: nonpregnant_adult|pregnancy} → ADA 2026 A1C, pre/postprandial and CGM targets, or pregnancy targets.","POST   /diabetes/carb-plan           (API key) → body {carb_g_per_day | energy_kcal+carb_pct | sex, meals?, snacks?, population?: general|gdm, meal_weights?} → carbohydrate per meal and snack in grams and 15 g exchanges.","POST   /diabetes/carb-count          (API key) → body {total_carb_g, fibre_g?, sugar_alcohol_g?, servings?} → counted carbohydrate from a food label (KR half-fibre rule).","POST   /diabetes/exchange-plan       (API key) → body {exchanges?: {type: n}, meals?: {meal: {type: n}}, carb_plan?: {...}, exchange_type?, q?, limit?} → links carbohydrate targets to exchange_lists. Read only.","POST   /diabetes/glycemic-load       (API key) → body {foods: [{food, carbs_g, gi?}]} → GI from glycaemic_index_data, GL per food and meal. Read only.","POST   /diabetes/insulin             (API key) → body {tdd_units|icr_g_per_unit+cf_mg_dl_per_unit, carbs_g?, current_glucose_mg_dl?, target_glucose_mg_dl?} → 500 and 1700 rules, mealtime and correction dose. Starting estimate for the prescriber.","POST   /diabetes/hypoglycemia        (API key) → body {glucose_mg_dl|glucose_mmol_l, assistance_needed?, able_to_swallow?, on_alpha_glucosidase_inhibitor?} → ADA level 1-3 and treatment steps.","POST   /diabetes/exercise            (API key) → body {duration_min, on_insulin_or_secretagogue?, pre_exercise_glucose_mg_dl?, intensity?, ketones_present?} → extra carbohydrate for activity.","POST   /diabetes/plan                (API key) → body {labs?, population?, carb?: {...}} → status, glucose targets and carbohydrate plan in one call.","GET    /weight-management/reference   (API key) → chapter 21 tables: BMI classes, Table 21.3 risk, metabolic syndrome, rate bands, energy floors, AMDR, diet adequacy, activity targets, Table 21.11/21.12, known issues. Optional ?section=","GET    /weight-management/bariatric   (API key) ?procedure=lagb|lsg|rygb|bpd_ds [&sex=&childbearing_age=true&pregnant=true] → diet stages (Table 21.9) and vitamin/mineral supplementation (Table 21.10, misprints flagged)","POST   /weight-management/assess      (API key) → adult {sex, age_years, weight_kg, height_cm, waist_cm?, hip_cm?, body_fat_percent?, usual_weight_kg+period_days?, comorbidity_count?, systolic_mmhg?, diastolic_mmhg?, triglycerides_mg_dl?, hdl_mg_dl?, fasting_glucose_mg_dl?} or child (2-18 y) {sex, age_years, weight_kg, height_cm | bmi_for_age_percentile, secondary_complications?} → BMI class, risk, metabolic syndrome, treatment options or age-based goal","POST   /weight-management/loss-plan   (API key) → {sex, age_years, weight_kg, height_cm, pal_category|pal|activities|activity_log|activity_factor | maintenance_kcal, deficit_kcal? (500-1000) | intake_kcal?, target_weight_kg?, comorbidity_count?} → intake, rate, 5%/10% goals, protein 1.2 g/kg, macros, maintenance at goal; refuses under 800 kcal, age 65+, BMI under 25, pregnancy","POST   /weight-management/gain-plan   (API key) → {sex, age_years, weight_kg, height_cm, activity inputs | maintenance_kcal, surplus_kcal? (500-1000), target_weight_kg?} → maintenance + 500-1000 kcal, protein 12-15% and fat 30%, rate, target range, Table 21.12 boosters; refuses BMI 25 or more","POST   /weight-management/progress    (API key) → {goal: loss|gain|maintain, weigh_ins: [{date, weight_kg}], height_cm?, start_weight_kg?, target_weight_kg?, recent_days?, weight_before_loss_kg?} → % change, rate vs guideline, 5%/10% milestones, plateau, maintenance check","POST   /weight-management/diet-pattern (API key) → {carbohydrate_g, protein_g, fat_g, alcohol_g?, weight_kg?, age_years?, energy_restriction?, vlcd?} → energy, AMDR comparison, pattern tags, nutrients at risk, protein g/kg","GET    /sports/reference        (API key) → chapter 23 tables: activity factors, kcal/kg tiers, macro targets, fuel timing, hydration, body-fat standards, known chapter issues","POST   /sports/energy           (API key) → body {weight_kg, lean_body_mass_kg | body_fat_percent, activity_level (sedentary|lightly_active|moderately_active|very_active|extra_active) | activity_multiplier, exercise?: [{mets, minutes}], activity_log?: [{activity, type?, minutes|hours}], tef?, sex?, age_years?, height_cm?} → Cunningham REE, activity, TEF, total kcal, kcal/kg check","POST   /sports/macros           (API key) → body {weight_kg, profile: general_fitness|general_training|endurance|elite_endurance|resistance_hypertrophy, energy_kcal?, age_years?, intake?: {carbohydrate_g, protein_g, fat_g}} → g and g/kg ranges, protein per meal, AMDR and intake checks","POST   /sports/fuel-timing      (API key) → body {weight_kg, duration_min?, hours_before_event?, recovery_hours?, drink?: {carbohydrate_g, serving_g|serving_ml}} → pre-event, during-exercise and recovery carbohydrate, drink concentration check","POST   /sports/hydration        (API key) → body {weight_before_kg, weight_after_kg, fluid_intake_ml?, urine_ml?, duration_min?, urine_specific_gravity?, age_years?, ambient_c?, hours_to_next_session?} → body-mass loss %, rehydration volume, sweat estimate, in-exercise fluid and sodium, urine SG band","POST   /sports/body-composition (API key) → body {sex, weight_kg, body_fat_percent, age_years?, target_weight_kg?} → fat and lean mass, lowest safe and highest optimal weight, weekly loss/gain rates, weeks to target","POST   /sports/supplement-dose  (API key) → body {weight_kg, age_years, supplements?: [caffeine|creatine|beta_alanine|hmb|beetroot_juice], pregnant?, lactating?} → chapter doses; withheld under 18, pregnant or nursing","POST   /sports/plan             (API key) → one athlete: top-level body and activity fields plus optional profile, event, hydration, weight_goal, supplements objects → each section as its own endpoint would return it","POST   /surveillance/records    (API key) → one anonymised screening record, or {records:[...]} up to 100: {submission_id, consent_confirmed:true, observed_on?, district, sex?, age_months|age_years, setting?, source?, measurements?:{weight_kg,length_cm|height_cm,muac_cm}, outcomes?:{...}}; measurements are classified (WHO) and discarded; unknown fields are rejected; needs SURVEILLANCE_STORE=true and sql/037","GET    /surveillance/reference  (API key) → districts, population groups, indicators and statuses, accepted fields, privacy rules, method notes","GET    /surveillance/summary    (admin key) → records by group, setting and region, districts reporting, national prevalence per indicator (?from=&to=&months=)","GET    /surveillance/hotspots   (admin key) → districts significantly above the national figure for an indicator (?population_group=&indicator=&min_n=); pass indicator to list every district","GET    /surveillance/trends     (admin key) → monthly series, trend test and change vs the same month last year (?indicator= required, ?population_group=&district=&months=)","GET    /surveillance/alerts     (admin key) → early warning: districts where the recent months are higher than the baseline (?recent_months=3&baseline_months=9&min_increase_pp=2)","GET    /surveillance/insights   (admin key) → hotspots, rising districts, national trends and data gaps as prioritised insights with suggested actions and research questions","GET    /energy/factors         (API key) → activity and stress factors, PAL categories, activity table, IOM equations, reference tables","GET    /admin/usage      (root key only) → calls per endpoint, daily totals, top routes and estimated p95 latency from route_usage (?days=, max 30); needs ROUTE_USAGE_STORE=true and sql/032 (sql/036 for p95)","GET    /admin/health-history (root key only) → uptime %, per-upstream uptime and latency, outage timeline from the 5-minute health probe (?hours=, max 720); needs HEALTH_CHECK_STORE=true and sql/034","GET    /admin/monitoring (root key only) → failed calls and slow endpoints from request_log (?hours=, max 168); needs REQUEST_LOG_STORE=true and sql/031","GET    /admin/activity   (root key only) → recent admin actions: keys created/revoked, packaged foods approved/rejected (?limit=, max 100)","GET    /admin/overview   (root key only) → read-only snapshot for the admin monitor: registry record counts, packaged review queue, RAG ingest jobs, API key and usage totals","GET    /admin/keys       (root key only) → list keys (never returns the raw key or its hash)","POST   /admin/keys       (root key only) → body {label, role?}; role: admin (default, full access) | reviewer (packaged review + reads only); returns the raw key ONCE, store it now","DELETE /admin/keys/:id   (root key only) → revoke (soft — sets revoked_at, doesn't delete the row)"],"foods":["GET  /foods?search=...&category=...  → list/search results are trimmed to identity + amount (measure/weight_g) + energy + macros + micros (see FOODS_SEARCH_FIELDS); GET /foods/:id still returns the full row incl. mfct_code/moisture_g/etc.","GET  /foods?with_servings=true","GET  /foods/:id?with_servings=true     → add ?with_servings=true for a serving_sizes[] array (household measures, e.g. \"1 cup\", each with nutrients pre-scaled from the 100g basis)","GET  /foods/lookup?q=...|barcode=...&with_servings=true&tier=wider   (API key, rate-limited) → CNR's wider data tier: local cache → USDA FDC → Open Food Facts → FatSecret; ?with_servings=true adds serving_sizes[] as above. ?tier=wider skips the local/fuzzy steps and forces USDA/OFF/FatSecret directly — for when the local Malawi FCT entry is a different preparation than what was actually asked for (e.g. 'rice, cooked' vs. the local 'Rice, soaked' entry). All sources now include fiber + the same micronutrient panel as public.foods where available — FatSecret is the exception for vitamin_a/vitamin_c/calcium/iron specifically (its API only gives %DV for those four, not absolute values, so they're left null rather than guessed; see raw_data.*_pct_dv on fatsecret-sourced results)","GET  /foods/search?q=&max_results=&min_similarity=&category=&brand=&ingredient=  (API key, rate-limited) → typo-tolerant local search over public.foods (pg_trgm word_similarity + levenshtein ranking, see sql/008_add_fuzzy_food_search.sql + sql/009_add_advanced_food_search.sql), expanded through food_synonyms (Chichewa<->English + local-name aliases — a search for 'chinangwa' also searches 'cassava'). category= filters/narrows the q search (or, with no q, browses that category). brand= and ingredient= search packaged_foods.brand/.ingredients_text instead (brand search, search-by-ingredient) and merge into the same results array. Every result is tagged _source ('foods'|'packaged_foods') and _match_type ('fuzzy'|'synonym'|'category'|'brand'|'ingredient'). No external API calls, never caches an external result — purely local. Also used internally as a fallback tier in lookupFoodCascade (/foods/lookup and everything built on it) when the exact ilike match misses.","GET  /foods/by-category?category=...&max_results=  (API key, rate-limited) → plain category browse over public.foods, no query text needed","GET  /foods/baby-foods?search=&limit=&cursor=  (API key, rate-limited) → only the \"Baby foods\" rows (infant cereals, jarred baby food). GET /foods?search= leaves these out unless the query has a baby word, category=Baby foods, or include_baby_foods=true","GET  /foods/local-categories           (API key, rate-limited) → the category values public.foods actually uses, each with a count; use these with /foods/by-category and category=","GET  /foods/autocomplete?q=...&max_results=  (API key, rate-limited) → local-first as-you-type suggestions: Malawi FCT + packaged foods (brand/product) substring match, backfilled with fuzzy search for typo tolerance ('nsim' -> Nsima, Nsima ya chimanga, Nsima ya kondowole, ...), then food_synonyms expansion, then FatSecret Premier suggestions last for anything with no local coverage (skipped, not fatal, if FatSecret isn't configured)","GET  /foods/categories                 (API key, rate-limited, cached 24h) → FatSecret Premier food category list (a different taxonomy from the local table; see /foods/local-categories)","GET  /foods/substitutes?food_name=chicken&limit=  (API key, rate-limited, cached 1h) → Malawi-specific substitution suggestions, ranked by nutritional closeness, with a full nutrient comparison vs the original — see SUBSTITUTION_GROUP_OVERRIDES","GET  /foods/convert?q=rice&amount=2&unit=cup   (API key, rate-limited, cached 1h; also GET /foods/:id/convert?amount=&unit= and ?id=) → one food, one amount, either way round. Give a household measure (tsp, tbsp, dsp, h dsp, cup, us cup, scoop 16, 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. Always returns standard {grams, kilograms, milliliters, other_weight_units}, household {servings[] (the food's own chipande/cup/piece as fractional counts), volume {suggested, all[]} (spoons and cups, only when the food has a density from its own cup/spoon serving)}, nutrients for that amount, density_g_per_ml, and assumptions[] (1 g/ml is assumed and flagged for a volume with no density on file). Same food matching and gram conversion as POST /recipes/calculate. 'cup' is the 250 ml metric household cup (UCT guide); 'us cup' is 240 ml (Krause). amount accepts 2, 0.5, 1/2, 1 1/2","GET  /foods/compare?foods=nsima,rice,potatoes  (API key, rate-limited, cached 1h) → 2-6 foods side by side: per-100g energy/protein/carbs/fiber/fat/full micronutrient panel + nutrient_comparison (highest/lowest flagged per nutrient) + glycaemic block from glycaemic_index_data where a sourced GI/GL value exists (never estimated)","GET  /foods/:id/label?serving=  (API key, rate-limited, cached 1h) → Codex-oriented nutrition label for one food. References CXS 1-1985 (General Standard for the Labelling of Pre-packaged Foods, `labelling_standard`) and CXG 2-1985 (Guidelines on Nutrition Labelling, `nutrition_labelling_guideline`) separately. Legacy flat fields (serving_size, calories, total_fat_g, saturated_fat_g, carbohydrates_g, fiber_g, sugars_g, protein_g, sodium_mg, vitamins_minerals, standard) are unchanged; adds energy_kcal/energy_kj, a structured nutrition_declaration and vitamins_minerals_detail (each field paired with status: available/not_available/estimated — a genuine 0 is never shown as missing, and 'estimated' only appears where the source FCT's data_quality_flags itself flags that value as assumed), per_100g_reference (the raw 100g basis a scaled serving was derived from), and a calculation block (serving_weight_source: measured/estimated, value_basis: direct_from_database/calculated_from_per_100g_reference). Defaults to the most specific serving Serving-Size Intelligence has (chipande/mtanda, cup, tablespoon, handful, piece, etc. — see SERVING_SIZE_KEYWORDS); ?serving= picks another by label text (e.g. ?serving=100g) — an unrecognized ?serving= falls back to the default rather than erroring, flagged via requested_serving.matched=false. missing_fields flags any mandatory value this food doesn't have data for. Invalid (non-numeric) food IDs and non-positive serving weights return 400/422 with a clear message. POST /recipes/calculate returns the same nutrition_label shape for a whole recipe (its own per-serving totals — no single food row, so no per_100g_reference or quality-flag-based 'estimated' status there)","POST /foods            (admin)","POST /foods/bulk       (admin) → body {items:[...]}, max 500, batch insert in one request","PUT  /foods/:id        (admin)","PATCH /foods/:id       (admin)","DELETE /foods/:id      (admin)"],"glycaemic_index":["GET  /glycaemic-index?search=...      → published GI/GL reference rows (see sql/004_add_glycaemic_index.sql); backs the glycaemic block on GET /foods/compare","GET  /glycaemic-index/:id","POST /glycaemic-index         (admin) → requires food_name, match_keywords[], source (citation) — no GI/GL value accepted without a traceable source","POST /glycaemic-index/bulk    (admin) → body {items:[...]}, max 500; seed file: scripts/glycaemic_index_seed.json","PUT  /glycaemic-index/:id     (admin)","PATCH /glycaemic-index/:id    (admin)","DELETE /glycaemic-index/:id   (admin)"],"bmi_for_age":["GET  /bmi-for-age/classify?sex=girls&age_months=95&weight_kg=26&height_cm=121.1  → WHO 2007 BMI-for-age status for 5y 1m to 19y 0m (or pass &bmi= instead of weight/height); screening aid, see sql/012_add_bmi_for_age.sql","GET  /bmi-for-age?sex=&age_months=   → raw reference rows (-3SD to +3SD cut-offs)","POST /bmi-for-age/bulk        (admin) → body {items:[...]}, 336 rows; seed file: scripts/bmi_for_age_seed.json"],"hc_velocity":["GET  /hc-velocity/classify?sex=girls&start_age_months=3&hc_start_cm=40.2&hc_end_cm=44.0  → WHO 6-month head circumference increment band (0-24 months; or pass &increment_cm= instead); screening aid, see sql/018_add_who_hc_velocity.sql","GET  /hc-velocity?sex=&start_age_months=   → raw reference rows (-3SD to +3SD increments, cm)","POST /hc-velocity/bulk        (admin) → body {items:[...]}, 38 rows; seed file: scripts/who_hc_velocity_seed.json"],"length_velocity":["GET  /length-velocity/classify?sex=girls&start_age_months=3&length_start_cm=61.0&length_end_cm=71.3  → WHO 6-month length increment band (0-24 months; or pass &increment_cm= instead); screening aid, see sql/019_add_who_length_velocity.sql","GET  /length-velocity?sex=&start_age_months=   → raw reference rows (-3SD to +3SD increments, cm)","POST /length-velocity/bulk    (admin) → body {items:[...]}, 38 rows; seed file: scripts/who_length_velocity_seed.json"],"weight_velocity":["GET  /weight-velocity/classify?sex=girls&start_age_months=3&weight_start_kg=5.8&weight_end_kg=8.2  → WHO 6-month weight increment band + exact z-score and percentile (0-24 months; or pass &increment_g= instead); screening aid, see sql/020_add_who_weight_velocity.sql","GET  /weight-velocity?sex=&start_age_months=   → raw reference rows (-3SD to +3SD increments, grams, plus L/M/S/Delta)","POST /weight-velocity/bulk    (admin) → body {items:[...]}, 38 rows; seed file: scripts/who_weight_velocity_seed.json"],"muac_for_age":["GET  /muac-for-age/classify?sex=girls&age_months=12&muac_cm=13.5  → WHO arm circumference-for-age band + exact z-score and percentile (3-60 months; or pass &muac_mm= instead); screening aid, see sql/021_add_who_muac_for_age.sql","GET  /muac-for-age?sex=&age_months=   → raw reference rows (-3SD to +3SD, cm, plus L/M/S)","POST /muac-for-age/bulk    (admin) → body {items:[...]}; seed file: scripts/muac_for_age_seed.json"],"muac":["GET  /muac/classify?age_months=18&muac_cm=11.8  → fixed-cut-off MUAC class (severe / moderate / normal), 6 months to adult; or pass &muac_mm=; add &scheme=cmam for the 115/125 mm community cut-offs (6-59 months). Calculation only, no table. Screening aid"],"length_height_for_age":["GET  /length-height-for-age/classify?sex=girls&age_months=18&length_cm=78  → WHO length/height-for-age stunting band + exact z-score and percentile (0-228 months = birth to 19 years; length = lying under 24 months, height = standing from 24; optional &measured=lying|standing converts by 0.7 cm if needed); screening aid, see sql/022_add_who_length_height_for_age.sql","GET  /length-height-for-age?sex=&age_months=   → raw reference rows (-3SD to +3SD, cm, plus L/M/S)","POST /length-height-for-age/bulk    (admin) → body {items:[...]}; 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)"],"weight_for_age":["GET  /weight-for-age/classify?sex=girls&age_months=12&weight_kg=8.5  → WHO weight-for-age underweight band + exact z-score and percentile (0-120 months = birth to 10 years; or pass &weight_g=); screening aid, see sql/024_add_who_weight_for_age.sql","GET  /weight-for-age?sex=&age_months=   → raw reference rows (-3SD to +3SD, kg, plus L/M/S)","POST /weight-for-age/bulk    (admin) → body {items:[...]}, 242 rows; seed file: scripts/weight_for_age_seed.json"],"weight_for_length_height":["GET  /weight-for-length-height/classify?sex=girls&age_months=18&weight_kg=9.2&length_cm=78  → WHO weight-for-length (under 24 months, lying) / weight-for-height (24-60 months, standing) wasting/overweight band + exact z-score and percentile; optional &measured=lying|standing converts by 0.7 cm; screening aid, see sql/025_add_who_weight_for_length_height.sql","GET  /weight-for-length-height?sex=&measured=&length_height_cm=   → raw reference rows (-3SD to +3SD, kg, plus L/M/S)","POST /weight-for-length-height/bulk    (admin) → body {items:[...]}, 2404 rows in five seed parts: scripts/weight_for_length_height_seed_1.json ... _5.json (max 500 per request)"],"bmi_for_age_under_5":["GET  /bmi-for-age-under-5/classify?sex=girls&age_months=18&weight_kg=9.2&length_cm=78  → WHO BMI-for-age band (0-60 months) + exact z-score and percentile; or pass &bmi= directly; optional &measured=lying|standing converts by 0.7 cm before BMI is computed; screening aid, see sql/026_add_who_bmi_for_age_under_5.sql. 5-19 years: /bmi-for-age","GET  /bmi-for-age-under-5?sex=&age_months=   → raw reference rows (-3SD to +3SD, kg/m2, plus L/M/S)","POST /bmi-for-age-under-5/bulk    (admin) → body {items:[...]}, 124 rows; seed file: scripts/bmi_for_age_under5_seed.json"],"hc_for_age":["GET  /hc-for-age/classify?sex=girls&age_months=6&hc_cm=42  → WHO head circumference-for-age band (microcephaly / macrocephaly) + exact z-score and percentile (0-60 months; or pass &hc_mm=); screening aid, see sql/027_add_who_hc_for_age.sql. 6-month increments: /hc-velocity","GET  /hc-for-age?sex=&age_months=   → raw reference rows (-3SD to +3SD, cm, plus L/M/S)","POST /hc-for-age/bulk    (admin) → body {items:[...]}, 122 rows; seed file: scripts/hc_for_age_seed.json"],"growth":["GET  /growth/assess?sex=girls&age_months=18&weight_kg=9.2&length_cm=78&hc_cm=45&muac_cm=13.5  → every applicable WHO indicator for the measurements given (length/height-for-age, weight-for-age, weight-for-length/height, BMI-for-age, head circumference-for-age, MUAC z-score and fixed MUAC), plus an attention list and acute-malnutrition signals; optional &measured=lying|standing and &muac_scheme=who_hiv_2008|cmam; at least one measurement required"],"fenton_preterm":["GET  /fenton-preterm/classify?sex=&metric=weight|length|hc&reference_year=2013|2025&gest_age_weeks=&day=&value=  → z-score, percentile, status (2013/2025 Fenton preterm growth chart; screening aid, see sql/013_add_fenton_preterm.sql)","POST /fenton-preterm/profile  → body {sex, reference_year?, gest_age_weeks, day?, weight?, length?, hc?} → classify weight+length+HC together at one timepoint (at least one metric required)","POST /fenton-preterm/growth   → body {sex, metric, reference_year?, measurements:[{gest_age_weeks, day?, value}, ...]} → classified trajectory + descriptive trend (percentile-crossing flags)","POST /fenton-preterm/velocity → same body shape as growth, 2+ points → g/kg/day (weight) or cm/week (length/hc) between each consecutive pair","GET  /fenton-preterm/chart?sex=&metric=&reference_year=&points=28w0d:900,30w0d:1200  → server-rendered SVG growth chart image (P3/P10/P50/P90/P97 curves, optional overlay points); never returns numeric curve data","GET  /fenton-preterm/references → static metadata: years, sexes, metrics, exact valid age ranges, citations, license summary — no L/M/S values","No list/dump route for the raw table — licensed from Dr. Tanis Fenton (CC BY-NC-ND 4.0, non-commercial, this app only); raw L/M/S values are never returned to end users, in any of the above.","POST /fenton-preterm/bulk     (admin) → body {items:[...]}, 2284 rows across 5 files; seed files: scripts/fenton_preterm_seed_1.json .. _5.json"],"fhir":["GET  /fhir/metadata (no key needed) → FHIR R4 CapabilityStatement","POST /fhir/Observation/$evaluate-growth → body: FHIR Parameters {sex, gestationalAgeWeeks+day OR birthDate, measurement:[{type: weight|length|hc|muac|bmi, value}, ...], measured?, muacScheme?, referenceYear?, effectiveDateTime?} → Observation or Bundle of Observations (birthDate: every WHO indicator, birth to 19 years)","POST /fhir/Bundle/$nutrition-assessment → NCP step 1 foundation: Patient + Encounter + anthropometry/biochemical/clinical/dietary/NFPE Observations + QuestionnaireResponses → one linked, validated Bundle","POST /fhir/QuestionnaireResponse/$validate → check a QuestionnaireResponse against a built-in form","GET  /fhir/Questionnaire, /fhir/Questionnaire/{nutrition-screening|nfpe|diet-history} (no key needed) → the built-in assessment forms","POST /fhir/Observation/$screen → NCP screening (MUST, adults)","POST /fhir/Observation/$assess-nutrition → NCP assessment: BMI, % weight change, energy/protein needs + intake, GLIM, FH/AD/BD/PD/CH indicators","POST /fhir/Condition/$diagnose → NCP diagnosis: PES statements → Condition","POST /fhir/CarePlan/$intervene → NCP intervention: goals, NutritionOrder, education/counseling/coordination → CarePlan Bundle","POST /fhir/Goal/$evaluate → NCP monitoring & evaluation: goal achievementStatus vs baseline/target","POST /fhir/CarePlan/$monitor → NCP step 4 follow-up: Observations/QuestionnaireResponses linked to patient, encounter and plan, change from baseline, Goal status, plan-review Tasks and a draft revised CarePlan","POST /fhir/Bundle/$ncp-document → complete ADIME note: Composition + document Bundle, identified and validated (structure, references, links)","POST /fhir/Bundle/$care-context → NCP step 6: Organization, Practitioner, PractitionerRole and CareTeam resources (who did the work, their role, the facility, the multidisciplinary team)","POST /fhir/Bundle/$validate-document → check any document Bundle; returns an OperationOutcome","GET  /fhir/CodeSystem/ncp-diagnosis (no key needed) → NCP nutrition diagnostic terminology (2006 ed., NI/NC/NB; stored in Supabase table ncp_diagnosis_terms)","GET  /fhir/CodeSystem/{name}, /fhir/OperationDefinition/{id}, /fhir/StructureDefinition/{name}, /fhir/ConceptMap/{id} (no key needed) → conformance resources for every canonical URL the facade emits (see /fhir/metadata)","Every other /fhir operation (all the POST operations above, ConceptMap/$translate and $validate included) needs 'Authorization: Bearer <API key>' with the right FHIR scope; consumer keys carry system/*.*. GET /fhir/AuditEvent needs the admin role.","Stateless facade, no storage. $evaluate-growth wraps fenton-preterm and the WHO indicators (length/height, weight, weight-for-length/height, BMI, head circumference, MUAC) from birth to 19 years. Not yet registered with any OpenHIM/HIE instance. See src/fhir.js and src/fhirNcp.js."],"exchange_lists":["GET  /exchange","POST /exchange         (admin)","POST /exchange/bulk    (admin) → body {items:[...]}, max 500","PUT  /exchange/:id     (admin)","PATCH /exchange/:id    (admin)","DELETE /exchange/:id   (admin)"],"renal":["GET  /renal","POST /renal            (admin)","POST /renal/bulk       (admin) → body {items:[...]}, max 500","PUT  /renal/:id        (admin)","PATCH /renal/:id       (admin)","DELETE /renal/:id      (admin)"],"enteral_formulas":["GET  /formulas","POST /formulas         (admin)","POST /formulas/bulk    (admin) → body {items:[...]}, max 500","PUT  /formulas/:id     (admin)","PATCH /formulas/:id    (admin)","DELETE /formulas/:id   (admin)"],"packaged_foods":["GET  /packaged","GET  /packaged/pending          (admin) → review queue (filters: source, limit, offset)","POST /packaged/submit           (API key, rate-limited — community contribution, status=pending)","POST /packaged/scan             (API key, rate-limited — photo of nutrition label -> OCR/AI -> status=pending)","POST /packaged/:id/approve      (admin) → status=approved (optional body = field corrections)","POST /packaged/:id/reject       (admin) → status=rejected (body requires 'reason')","PUT  /packaged/:id              (admin)","PATCH /packaged/:id             (admin)","DELETE /packaged/:id            (admin)"],"rag":["POST /rag/retrieve     (API key, rate-limited) → semantic search (query, context, top_k)","POST /rag/ingest       (admin) → add document chunk (content, source, context)","POST /rag/upload       (admin, multipart: file, source, context, replace) → auto-ingest PDF/DOCX/TXT/MD/CSV/HTML via queue","POST /rag/enqueue      (admin) → {path, source} for a file already in the rag-uploads bucket, or {job_id} to retry","GET  /rag/jobs[/:id]   (admin) → ingestion job status; list takes ?status=queued|processing|done|failed, ?limit= (max 100), ?offset=","DELETE /rag/source?source=... (admin) → bulk-delete all chunks for one document (use before re-ingesting a fixed document, since ingest never overwrites)","POST /rag/ask          (API key, rate-limited) → RAG Search Orchestrator: intent detection -> fan-out search (semantic + Malawi FCT + packaged/OCR foods + exchange/renal/formula DBs + barcode + USDA/OFF/FatSecret fallback) -> rerank -> grounded LLM answer with citations (query, context, top_k, session_id)"],"memory":["POST /memory/write        (API key, rate-limited) → capture a session fact (session_id, content)","POST /memory/recall       (API key, rate-limited) → top-K relevant memory for a session (session_id, query, top_k) — preferred; GET with the same params still works but is deprecated since it puts clinical text in the URL/logs","POST /memory/consolidate  (admin) → summarize a session's facts (session_id) — also run hourly by cron"],"favorites":["GET    /favorites?user_id=...&resource_type=...   (API key, rate-limited)","POST   /favorites   (API key, rate-limited) → body {user_id, resource_type, resource_id}, idempotent","DELETE /favorites   (API key, rate-limited) → body {user_id, resource_type, resource_id}"],"history":["GET  /history?user_id=...&resource_type=...   (API key, rate-limited) → recently viewed, viewed_at desc","POST /history   (API key, rate-limited) → body {user_id, resource_type, resource_id}, upserts viewed_at"],"food_log":["GET    /log?user_id=...&date=YYYY-MM-DD   (API key, rate-limited) → diary entries, newest first; date filter optional","GET    /log/:id                           (API key, rate-limited) → single entry","POST   /log        (API key, rate-limited) → body {user_id, meal_type: breakfast|lunch|snack|dinner, calories, food_name?, entry_date?}","DELETE /log/:id?user_id=...                (API key, rate-limited) → delete one entry (scoped to user_id)","GET    /log/summary?user_id=...&period=daily|weekly&date=YYYY-MM-DD   (API key, rate-limited) → kcal totals + by_meal breakdown; weekly adds by_date[] and average_daily_calories"],"recipes":["POST /recipes/calculate   (API key, rate-limited) → body {servings?, ingredients:[{food_id?|food_name, quantity, unit?}]}; resolves each ingredient (local foods, falling back to the same local→external cascade as /foods/lookup), converts quantity/unit to grams (see Serving-Size Intelligence), and returns total_nutrients + nutrients_per_serving + a per-ingredient breakdown + any unresolved_ingredients"],"paediatric":["POST /paediatric/energy   (API key) → body {sex, age_months|age_years, weight_kg, height_cm?, pal_category?, activity_level?|activity_factor?, stress_condition?|stress_factor?} → Schofield and WHO 1985 BMR, BMR x activity x stress (range), DRI/FAO 2004 kcal/kg/day, IOM EER, recommended, macronutrient grams by AMDR","POST /paediatric/protein   (API key) → {age_months|age_years, weight_kg, clinical_status? healthy|sick, energy_kcal?} → IOM healthy-growth and ASPEN sick-child g/kg/day and g/day","POST /paediatric/fluid   (API key) → {weight_kg} → Holliday-Segar maintenance (mL/day, mL/kg/day, mL/hr)","POST /paediatric/preterm   (API key) → {weight_g, monitoring?} → preterm fluid, energy and protein by weight band, and growth monitoring against targets","POST /paediatric/growth-velocity   (API key) → {sex, age_months, days, weight_g_start/end, height_cm_start/end} → weight g/day and length mm/day against the reference velocity","POST /paediatric/catch-up   (API key) → {sex, age_months, weight_kg, height_cm?|ideal_weight_kg?, weight_age_months?} → catch-up kcal/kg and g/kg (weight-age method; under 5 years the WHO median weight-for-length/height is read from who_weight_for_length_height) and the growth-failure stress-factor estimate","POST /paediatric/enteral-feeds   (API key) → {age_months, weight_kg, route? continuous|bolus, formula_kcal_per_ml?, energy_target_kcal?} → initiation, advancement and goal for the weight, steps and hours to goal, goal volume and energy","POST /paediatric/plan   (API key) → energy, protein, fluid and macronutrients in one call; optional catch_up:true or ideal_weight_kg, and feeding_route","GET  /paediatric/reference[?section=]   (API key) → the tables behind the calculators and their sources. /pediatric works as an alias"],"micronutrients":["POST /micronutrients/assess   (API key) → {age, sex | life_stage, intake {iron_mg, calcium_mg, ...} | days:[{intake}], plant_based_diet?} → likely inadequate / borderline / possibly low / above UL, with EAR, RDA, AI and UL beside each intake","GET  /micronutrients/requirements?age=&sex=[&life_stage_type=&group=vitamin|mineral]   (API key) → EAR, RDA, AI, UL for the life stage","GET  /micronutrients/reference   (API key) → nutrients covered, request fields and the adequacy rules"],"enteral-nutrition":["POST /enteral-nutrition/calculate   (API key) → {formula_id | formula_name | formula:{kcal_per_ml, protein_g_per_l, water_ml_per_l}, kcal_needs, protein_needs_g?, fluid_needs_ml?, weight_kg?, mode?, hours_per_day?, refeeding_risk?, ...} → volume, rate, kcal, protein, formula water, free water flush, adequacy and 65% delivery scenario","GET  /enteral-nutrition/reference[?section=]   (API key) → steps, limits, modes and request fields"],"critical-care":["POST /critical-care/protein-by-stress   (API key) → {stress_level? 0-3, weight_kg?} → g/kg/day (and g/day) by metabolic stress; omit stress_level for the whole table","POST /critical-care/npe-nitrogen-ratio   (API key) → {npe_kcal, protein_g} → NPE:N2 ratio and stress interpretation","POST /critical-care/nitrogen-balance   (API key) → {urine_volume_l, urinary_urea_mmol_l} → urinary N, total urinary N, total N output and the stress grade (general ICU, not burns)","POST /critical-care/carbohydrate   (API key) → {weight_kg?} → usual, minimum and maximum carbohydrate, with g/day when weight is sent","GET  /critical-care/fat   (API key) → usual, minimum and maximum fat, acute pancreatitis rule","POST /critical-care/propofol-fat   (API key) → {propofol_percent: 1|2, volume_ml} → propofol mg and fat g delivered","GET  /critical-care/cho-fat-ratio   (API key) → CHO:fat ratios of NPE by indication","POST /critical-care/electrolytes   (API key) → {electrolyte? phosphate|potassium|magnesium} → replacement dosing","POST /critical-care/potassium-deficit   (API key) → {serum_k_mmol_l} → deficit band and expected rise per 20 mmol KCl","POST /critical-care/fluid   (API key) → {weight_kg} → 30-35 mL/kg/day reference range","GET  /critical-care/reference[?section=]   (API key) → the tables behind the calculators and the known issues in the source"],"dietary-assessment":["POST /dietary-assessment   (API key, rate-limited) → body {age|life_stage, sex?, life_stage_type?, weight_kg?, height_cm?, pal_category?, trimester?, months_postpartum?, energy_target_kcal?, suggest_foods?, days:[{date?, meals:[{meal_type?, ingredients}] | ingredients | intake:{totals}}]} (1-7 days, max 40 ingredients; one day may be sent at the top level) → average intake vs DRI (EAR/RDA/AI/UL, sodium CDRR) per nutrient incl. 11 extra nutrients, energy vs IOM EER, macro % vs AMDR, food-group coverage, ranked gaps (likely_inadequate / borderline / possibly_low) and excesses, confidence from data coverage, and the foods richest in the top gaps; screening only, not a diagnosis","GET  /dietary-assessment/reference   (API key) → assessable nutrients, status definitions, accepted input, method notes"],"meals":["POST /meals/analyze   (API key, rate-limited) → body {meal_type?, ingredients:[{food_id?|food_name, quantity, unit?}], daily_targets?:{kcal,protein_g,carbs_g,fat_g}}; same ingredient resolution as /recipes/calculate, no servings — returns total_nutrients, macronutrient_breakdown (kcal + % from protein/carbs/fat, Atwater 4/4/9, compared against the standard adult AMDR range), food_groups_present/food_groups_missing (from the core Grains/Legumes/Protein/Vegetables/Fruits/Dairy set), and — only if daily_targets was supplied — daily_target_comparison; purely descriptive, never invents a personalized target itself"],"measures":["GET  /measures   (API key, rate-limited, cached 24h) → every unit (id, ml or grams, system, aliases, source), the printed reference tables as published (UCT household guide, kitchen conversion tables, Krause Appendix 2: liquid equivalents, dry measure, weights, metric conversion factors) and the #6-#60 portion scoop sizes. ?section=units|scoop_sizes|reference_tables|sources returns one part","GET  /measures/convert?amount=3&from=tbsp&to=ml   (API key, rate-limited, cached 24h) → unit to unit with no food: volume to volume, weight to weight; omit 'to' for every unit of that kind. Weight-to-volume needs a food: use GET /foods/convert"],"ingredients":["POST /ingredients/parse   (API key, rate-limited) → body {text}; parses free text like \"2 eggs, 1 cup rice, 100g chicken and ½ avocado\" into ingredients:[{food_name, quantity, unit}] — the exact shape /recipes/calculate and /meals/analyze accept. Groq LLM parse, falls back to a local regex parser if GROQ_API_KEY isn't configured or the LLM call fails. A bare count with no stated unit (\"2 eggs\") comes back as unit:\"serving\", not grams."],"drug_interactions":["GET  /drug-interactions?category=&severity=&limit=&offset=/cursor=   (API key, rate-limited) → paginated list of drug-nutrient interaction entries","GET  /drug-interactions/:id   (API key, rate-limited) → single entry","GET  /drug-interactions/search?q=warfarin   (API key, rate-limited) → keyword scan across drug/aliases/category/subcategory/tags/effects/implications (same approach as /rag/ask's exchange/renal/formula lookups) — 'drug' also accepted as the param name","POST /drug-interactions   (admin) → body: {drug, aliases?, category?, subcategory?, effects?, implications?, severity?, tags?}","POST /drug-interactions/bulk   (admin) → body {items:[...]}, up to 500 rows","PUT/PATCH/DELETE /drug-interactions/:id   (admin)"],"dri":["GET  /dri/life-stages   (API key, rate-limited) → every DRI life-stage group (code, label, sex, age range)","GET  /dri?nutrient=&life_stage=   OR   ?nutrient=&age=&sex=&life_stage_type=   (API key, rate-limited) → EAR/RDA/AI/UL for one nutrient (or every nutrient if 'nutrient' is omitted) at the resolved life stage, plus that life stage's AMDR and sodium CDRR when returning the full set","POST /dri/compare   (API key, rate-limited) → body {age|life_stage, sex?, life_stage_type?, intake:{nutrient_key: amount}}; compares intake (same field names as /recipes/calculate or /meals/analyze's total_nutrients) against RDA/AI, flags UL if exceeded — only nutrients CNR's foods table actually tracks are comparable, see 'trackable' on GET /dri"],"batch":["POST /batch   (API key, rate-limited, max 20 sub-requests) → body {requests:[{id?, method, path, body?}, ...]}; runs each sub-request through the same auth/rate-limit/cache pipeline as calling it directly (forwards Authorization/apikey/CF-Connecting-IP from the batch call), in parallel, and returns {status:'success', data:[{id, status, body}, ...], meta:{total, succeeded, failed, duration_ms}} with one entry per sub-request in the original order — one failed sub-request doesn't fail the others. Nested /batch sub-requests are rejected."]}}}