{"openapi":"3.1.0","info":{"version":"1.0.0","title":"Zwela LangGraph API","description":"HTTP API for Zamara Wellness: fitness and nutrition plans, Kenyan and FatSecret food data, wellness feeds (PubMed, mindspace, hidden gem), vision scans, chat, Connect (Agora RTC tokens), and zwela-admin tools. Interactive docs: GET /docs (Scalar), GET /ui (Swagger UI); OpenAPI JSON: GET /doc. **Authentication:** all routes except `/`, `/health*`, `/doc`, `/docs`, `/ui` require a Firebase ID token (`Authorization: Bearer`) or the zwela internal secret (same as `ZWELA_LANGGRAPH_INTERNAL_SECRET`). **Ops:** structured JSON logs to stdout and OpenTelemetry (OTLP HTTP) when `OTEL_EXPORTER_OTLP_ENDPOINT` is set. **Health:** `GET /health` (full), `/health/live`, `/health/ready`."},"tags":[{"name":"fitness","description":"Exercise catalog, plans, gym log, and zwela-admin exercise CRUD (`/fitness`)."},{"name":"nutrition","description":"Diets, Kenyan catalogue, FatSecret proxy, meal suggestions, plan jobs (`/nutrition`)."},{"name":"wellness","description":"PubMed, hidden gem, daily hooks, mindspace prompts (`/wellness`). Wellness stat snapshots are ingested by **zwela-timeseries** `POST /v1/wellness/timeseries/sync`."},{"name":"wellsphere","description":"Chronic conditions, medicines, passport and metric analysis (`/wellsphere`)."},{"name":"chat","description":"Zwela conversational agent (`/chat`)."},{"name":"scan","description":"Vision scans — food, prescription, receipt (`/scan`)."},{"name":"friends","description":"Onboarding embedding and org-scoped friend matching (`/friends`)."},{"name":"connect","description":"Connect social — Agora RTC token minting for voice/video (`/connect`)."},{"name":"general","description":"Shared utilities not tied to a single feature area (`/image`, etc.)."},{"name":"health","description":"Liveness, readiness, and deployment metadata (`/health`, `/health/live`, `/health/ready`)."}],"components":{"schemas":{"FitnessPlanOutput":{"type":"object","properties":{"id":{"type":"string"},"userId":{"type":"string"},"title":{"type":"string"},"description":{"type":"string"},"totalWeeks":{"type":"number"},"weeks":{"type":"array","items":{"$ref":"#/components/schemas/FitnessWeek"}},"planGoals":{"type":"array","items":{"type":"string"}},"guidelines":{"type":"array","items":{"type":"string"}},"nutritionRecommendations":{"type":"array","items":{"type":"string"}},"recoveryTips":{"type":"array","items":{"type":"string"}},"safetyWarnings":{"type":"array","items":{"type":"string"}},"citations":{"type":"array","items":{"$ref":"#/components/schemas/Citation"}},"createdAt":{"type":"string"}},"required":["id","userId","title","description","totalWeeks","weeks","planGoals","guidelines","nutritionRecommendations","recoveryTips","safetyWarnings","citations","createdAt"]},"FitnessWeek":{"type":"object","properties":{"weekNumber":{"type":"number"},"name":{"type":"string"},"description":{"type":"string"},"weeklyFocus":{"type":"array","items":{"type":"string"}},"workoutDays":{"type":"array","items":{"$ref":"#/components/schemas/WorkoutDay"}},"goals":{"type":"array","items":{"type":"string"}},"tips":{"type":"array","items":{"type":"string"}},"expectedOutcomes":{"type":"array","items":{"type":"string"}}},"required":["weekNumber","name","description","weeklyFocus","workoutDays","goals","tips","expectedOutcomes"]},"WorkoutDay":{"type":"object","properties":{"id":{"type":"string"},"dayNumber":{"type":"number"},"name":{"type":"string"},"description":{"type":"string"},"focusAreas":{"type":"array","items":{"type":"string"}},"exercises":{"type":"array","items":{"$ref":"#/components/schemas/Exercise"}},"estimatedDurationMinutes":{"type":"number"},"estimatedCalories":{"type":"number"},"preWorkoutNotes":{"type":"array","items":{"type":"string"}},"postWorkoutNotes":{"type":"array","items":{"type":"string"}},"isRestDay":{"type":"boolean"}},"required":["id","dayNumber","name","description","focusAreas","exercises","estimatedDurationMinutes","estimatedCalories","preWorkoutNotes","postWorkoutNotes","isRestDay"]},"Exercise":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"description":{"type":"string"},"type":{"type":"string","enum":["strength","cardio","flexibility","balance","warmup","cooldown"]},"difficulty":{"type":"string","enum":["beginner","intermediate","advanced"]},"targetMuscles":{"type":"array","items":{"type":"string"}},"sets":{"type":"number"},"repsPerSet":{"type":"number"},"durationSeconds":{"type":"number"},"restSeconds":{"type":"number"},"equipment":{"type":"array","items":{"type":"string"}},"instructions":{"type":"array","items":{"type":"string"}},"ttsStartInstruction":{"type":"string"},"ttsSetStartInstruction":{"type":"string"},"ttsSetEndInstruction":{"type":"string"},"ttsRestInstruction":{"type":"string"},"ttsCompletionInstruction":{"type":"string"},"commonMistakes":{"type":"array","items":{"type":"string"}},"formTips":{"type":"array","items":{"type":"string"}},"caloriesPerSet":{"type":"number"},"orderIndex":{"type":"number"}},"required":["id","name","description","type","difficulty","targetMuscles","sets","repsPerSet","durationSeconds","restSeconds","equipment","instructions","ttsStartInstruction","ttsSetStartInstruction","ttsSetEndInstruction","ttsRestInstruction","ttsCompletionInstruction","commonMistakes","formTips","caloriesPerSet","orderIndex"]},"Citation":{"type":"object","properties":{"sourceTitle":{"type":"string"},"sourceUrl":{"type":"string"},"dateCited":{"type":"string"}},"required":["sourceTitle","sourceUrl","dateCited"]},"FitnessProfileInput":{"type":"object","properties":{"organizationId":{"type":"string","example":"org_abc"},"userId":{"type":"string","example":"user_123"},"username":{"type":"string","example":"johndoe"},"bio":{"type":"string","example":"Fitness enthusiast"},"fitnessGoals":{"type":"array","items":{"type":"string"},"example":["weight_loss","muscle_gain"]},"fitnessLevel":{"type":"string","example":"Beginner"},"focusAreas":{"type":"array","items":{"type":"string"},"example":["chest","back"]},"preferredWorkoutTimes":{"type":"array","items":{"type":"string"}},"workoutDuration":{"type":"string","example":"45_min"},"workoutFrequency":{"type":"string","example":"3_per_week"},"limitations":{"type":"array","items":{"type":"string"}},"experienceLevel":{"type":"string","enum":["beginner","intermediate","advanced"],"example":"beginner"},"trainingLocation":{"type":"string","example":"gym"},"targetMuscle":{"type":"string","example":"Chest"},"equipment":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"string"}],"example":["Dumbbells","Bench"]},"workoutDurationMinutes":{"type":"number","example":45},"fitnessGoal":{"type":"string","example":"weight_loss"}},"additionalProperties":{}},"ExerciseSearchResponse":{"type":"object","properties":{"exercises":{"type":"array","items":{"$ref":"#/components/schemas/ExerciseSearchItem"}},"total":{"type":"number"}},"required":["exercises"]},"ExerciseSearchItem":{"type":"object","properties":{"id":{"type":"string","description":"Exercise ID from catalog (use for build-plan-from-selections)"},"name":{"type":"string"},"difficulty":{"type":"string"},"primaryEquipment":{"type":"string"},"primaryMuscle":{"type":"string"},"targetMuscle":{"type":"string"},"targetMusclesList":{"type":"array","items":{"type":"string"}},"equipmentList":{"type":"array","items":{"type":"string"}},"youtubeDemo":{"type":["string","null"]},"youtubeExplanation":{"type":["string","null"]}},"required":["id","name","difficulty","primaryEquipment","primaryMuscle","targetMuscle"]},"ExerciseSearchRequest":{"type":"object","properties":{"query":{"type":"string","default":"","example":"arms dumbbell"},"equipment":{"type":"array","items":{"type":"string"},"example":["Dumbbells"]},"difficulty":{"type":"string","example":"beginner"},"muscleGroup":{"type":"string","example":"Biceps"},"topK":{"type":"number","minimum":1,"maximum":100,"default":30},"offset":{"type":"number","minimum":0,"default":0}}},"BuildPlanFromSelectionsRequest":{"type":"object","properties":{"userId":{"type":"string","example":"user_123"},"totalWeeks":{"type":"number","minimum":1,"maximum":52,"default":1},"weekDays":{"type":"array","items":{"$ref":"#/components/schemas/PlanDaySelection"},"description":"Per-day selected exercise IDs"}},"required":["userId","weekDays"]},"PlanDaySelection":{"type":"object","properties":{"dayNumber":{"type":"number","example":1},"dayName":{"type":"string","example":"Monday"},"exerciseIds":{"type":"array","items":{"type":"string"},"description":"Exercise IDs from search (exercise.json catalog)"}},"required":["dayNumber","exerciseIds"]},"FriendsMatchResponse":{"type":"object","properties":{"userIds":{"type":"array","items":{"type":"string"}},"scores":{"type":"array","items":{"type":"object","properties":{"userId":{"type":"string"},"score":{"type":"number"}},"required":["userId","score"]}}},"required":["userIds","scores"]},"EmbedOnboardingResponse":{"type":"object","properties":{"ok":{"type":"boolean","enum":[true]},"userId":{"type":"string"},"created":{"type":"boolean","description":"true when a new embedding was generated; false if it already existed"}},"required":["ok","userId","created"]},"NutritionPlanOutput":{"type":"object","properties":{"id":{"type":"string"},"userId":{"type":"string"},"name":{"type":"string"},"description":{"type":"string"},"stages":{"type":"array","items":{"$ref":"#/components/schemas/PlanStage"}},"totalDurationWeeks":{"type":"number"},"planGoals":{"type":"array","items":{"type":"string"}},"motivationalTips":{"type":"array","items":{"type":"string"}},"recommendedNutrients":{"type":"array","items":{"type":"string"}},"suggestedSupplements":{"type":"array","items":{"type":"string"}},"potentialChallenges":{"type":"array","items":{"type":"string"}},"successIndicators":{"type":"array","items":{"type":"string"}},"citations":{"type":"array","items":{"$ref":"#/components/schemas/Citation"}},"createdAt":{"type":"string"}},"required":["id","userId","name","description","stages","totalDurationWeeks","planGoals","motivationalTips","recommendedNutrients","suggestedSupplements","potentialChallenges","successIndicators","citations","createdAt"]},"PlanStage":{"type":"object","properties":{"stageNumber":{"type":"number"},"name":{"type":"string"},"description":{"type":"string"},"durationWeeks":{"type":"number"},"calorieTarget":{"type":"number"},"proteinTarget":{"type":"number"},"carbsTarget":{"type":"number"},"fatsTarget":{"type":"number"},"guidance":{"type":"array","items":{"type":"string"}},"focusAreas":{"type":"array","items":{"type":"string"}},"expectedOutcomes":{"type":"array","items":{"type":"string"}},"suggestedFoods":{"type":"array","items":{"type":"string"}},"avoidFoods":{"type":"array","items":{"type":"string"}},"hydrationGoal":{"type":"string"},"mealFrequency":{"type":"string"},"sleepRecommendation":{"type":"string"},"stressManagementTips":{"type":"array","items":{"type":"string"}}},"required":["stageNumber","name","description","durationWeeks","calorieTarget","proteinTarget","carbsTarget","fatsTarget","guidance","focusAreas","expectedOutcomes","suggestedFoods","avoidFoods","hydrationGoal","mealFrequency","sleepRecommendation","stressManagementTips"]},"NutritionProfileInput":{"type":"object","properties":{"organizationId":{"type":"string","example":"org_abc"},"userId":{"type":"string"},"currentWeightKg":{"type":"number"},"heightCm":{"type":"number"},"targetWeightKg":{"type":"number"},"age":{"type":"number"},"sex":{"type":"string","example":"male"},"goal":{"type":"string","example":"lose"},"activityLevel":{"type":"string","example":"lightlyActive"},"dietaryPreferences":{"type":"array","items":{"type":"string"}},"allergies":{"type":"array","items":{"type":"string"}},"favoriteFoods":{"type":"array","items":{"type":"string"}},"dislikedFoods":{"type":"array","items":{"type":"string"}}},"required":["userId","currentWeightKg","heightCm","goal","activityLevel","dietaryPreferences","allergies","favoriteFoods","dislikedFoods"],"additionalProperties":{}},"ConditionFlutterResponse":{"type":"object","additionalProperties":{}},"ConditionQueryInput":{"type":"object","properties":{"conditionName":{"type":"string"},"userContext":{"type":"string"}},"required":["conditionName"]},"DrugFlutterResponse":{"type":"object","additionalProperties":{}},"MedicineQueryInput":{"type":"object","properties":{"drugName":{"type":"string"},"drugId":{"type":"string"},"userContext":{"type":"string"},"organizationId":{"type":"string"},"userId":{"type":"string","description":"Required with organizationId when using internal secret; otherwise Firebase uid is used."},"persistToFirestore":{"type":"boolean"}},"required":["drugName"]},"MetricAnalysisOutput":{"type":"object","properties":{"analysis":{"type":"string"}},"required":["analysis"]},"MetricAnalysisInput":{"type":"object","properties":{"metricName":{"type":"string"},"latestValue":{"type":"number"},"unit":{"type":"string"},"previousValue":{"type":"number"}},"required":["metricName","latestValue","unit"]},"PassportAnalysisOutput":{"type":"array","items":{"$ref":"#/components/schemas/Insight"}},"Insight":{"type":"object","properties":{"insight_heading":{"type":"string"},"insight_summary":{"type":"string"},"insight_points":{"type":"array","items":{"type":"string"}}},"required":["insight_heading","insight_summary","insight_points"]},"PassportAnalysisInput":{"type":"object","properties":{"whatMeasure":{"type":"string"},"result":{"type":"number"},"age":{"type":"string"},"height":{"type":"string"},"weight":{"type":"string"},"gender":{"type":"string"},"weightUnit":{"type":"string"},"heightUnit":{"type":"string"},"activityLevel":{"type":"string"},"promptType":{"type":"string","enum":["interpretation","recommendations"]}},"required":["whatMeasure","result","age","height","weight","gender","weightUnit","heightUnit","activityLevel","promptType"]},"WellsphereDailyHubResponse":{"type":"object","additionalProperties":{}},"WellsphereDailyHubInput":{"type":"object","properties":{"organizationId":{"type":"string"},"userId":{"type":"string","description":"Required when using internal secret; ignored when caller is a Firebase user (uid from token is used)."},"conditionName":{"type":"string","description":"When omitted, the server reads organizations/{organizationId}/wellsphere_profiles/{userId}.condition"},"date":{"type":"string","description":"Local calendar date YYYY-MM-DD; defaults to UTC today."},"forceRefresh":{"type":"boolean"},"userContext":{"type":"string"}},"required":["organizationId"]},"WellsphereManagementPlanResponse":{"type":"object","additionalProperties":{}},"WellsphereManagementPlanInput":{"type":"object","properties":{"organizationId":{"type":"string"},"userId":{"type":"string","description":"Required when using internal secret; ignored when caller is a Firebase user (uid from token is used)."},"conditionName":{"type":"string","description":"When omitted, read from wellsphere_profiles/{userId}.condition"},"month":{"type":"string","description":"Calendar month YYYY-MM (UTC); defaults to current UTC month."},"forceRefresh":{"type":"boolean"},"userContext":{"type":"string"}},"required":["organizationId"]},"WellsphereSymptomSuggestResponse":{"type":"object","additionalProperties":{}},"WellsphereSymptomSuggestInput":{"type":"object","properties":{"organizationId":{"type":"string"},"userId":{"type":"string"},"conditionName":{"type":"string","description":"When omitted, read from wellsphere_profiles/{userId}.condition"},"recentSymptomLabels":{"type":"array","items":{"type":"string"}},"userContext":{"type":"string"},"forceRefresh":{"type":"boolean"}},"required":["organizationId"]},"FoodScanOutput":{"type":"object","properties":{"name":{"type":"string"},"macro":{"type":"object","properties":{"calories":{"type":"number"},"protein":{"type":"number"},"carbs":{"type":"number"},"fats":{"type":"number"}},"required":["calories","protein","carbs","fats"]},"vitalityScore":{"type":"number","minimum":0,"maximum":100},"imageUrl":{"type":"string"}},"required":["name","macro","vitalityScore"]},"FoodScanInput":{"type":"object","properties":{"image_base64":{"type":"string"},"userId":{"type":"string"},"keyword":{"type":"string","default":"food"}},"required":["image_base64","userId"]},"PrescriptionScanOutput":{"type":"object","properties":{"prescriberName":{"type":"string"},"drugs":{"type":"array","items":{"$ref":"#/components/schemas/ScannedDrug"}},"notes":{"type":"string"}},"required":["prescriberName","drugs","notes"]},"ScannedDrug":{"type":"object","properties":{"drugName":{"type":"string"},"strength":{"type":"string"},"dosage":{"type":"string"},"timesPerDay":{"type":"number"}},"required":["drugName","strength","dosage","timesPerDay"]},"PrescriptionScanInput":{"type":"object","properties":{"image_base64":{"type":"string"}},"required":["image_base64"]},"ReceiptScanOutput":{"type":"object","properties":{"suggestedCategory":{"type":"string"},"businessName":{"type":"string"},"totalAmount":{"type":"number"},"receiptItems":{"type":"array","items":{"$ref":"#/components/schemas/ReceiptItem"}}},"required":["suggestedCategory","businessName","totalAmount","receiptItems"]},"ReceiptItem":{"type":"object","properties":{"itemCategory":{"type":"string"},"itemName":{"type":"string"},"itemAmount":{"type":"number"}},"required":["itemCategory","itemName","itemAmount"]},"ReceiptScanInput":{"type":"object","properties":{"image_base64":{"type":"string"},"categories":{"type":"array","items":{"type":"string"}}},"required":["image_base64","categories"]},"LangChatOutput":{"type":"object","properties":{"id":{"type":"string"},"text":{"type":"string"},"isUserMessage":{"type":"boolean"},"timestamp":{"type":"string"},"threadId":{"type":"string"},"attachments":{"type":"array","items":{"$ref":"#/components/schemas/Attachment"}},"navigation":{"$ref":"#/components/schemas/LangChatNavigation"}},"required":["id","text","isUserMessage","timestamp","threadId","attachments"]},"Attachment":{"type":"object","properties":{"id":{"type":"string"},"fileName":{"type":"string"},"fileType":{"type":"string"},"fileUrl":{"type":"string"}},"required":["id","fileName","fileType","fileUrl"]},"LangChatNavigation":{"type":"object","properties":{"route":{"type":"string"},"label":{"type":"string"}},"required":["route","label"]},"LangChatInput":{"type":"object","properties":{"userId":{"type":"string"},"organizationId":{"type":"string","minLength":1},"chatId":{"type":"string"},"text":{"type":"string"},"entryPoint":{"type":"string","enum":["general","fitness","nutrition","wellness","finance","legal","therapy","careManager","caremanager","financeAdviser","legalCounsel"]},"contextSource":{"type":"string","enum":["mood","journal","gratitude"]},"threadId":{"type":"string"},"attachments":{"type":"array","items":{"$ref":"#/components/schemas/Attachment"}},"history":{"type":"array","items":{"$ref":"#/components/schemas/LangChatMessageHistoryItem"}}},"required":["userId","organizationId","chatId","text","entryPoint"]},"LangChatMessageHistoryItem":{"type":"object","properties":{"text":{"type":"string"},"isUser":{"type":"boolean"}},"required":["text","isUser"]},"ConnectAgoraRtcTokenResponse":{"type":"object","properties":{"channelId":{"type":"string","description":"Agora channel — same as callId"},"uid":{"type":"integer","description":"Agora integer uid for this Firebase user"},"token":{"type":"string","description":"RTC token for joinChannel"},"expiresAt":{"type":"integer","description":"Unix seconds when the token expires (regenerate before this)"}},"required":["channelId","uid","token","expiresAt"]}},"parameters":{}},"paths":{"/fitness/generate-plan":{"post":{"tags":["fitness"],"description":"Generate a full fitness plan synchronously from the user profile (LLM). Use for interactive flows; prefer `/generate-plan-async` for long-running jobs.","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FitnessProfileInput"}}}},"responses":{"200":{"description":"Successfully generated fitness plan","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FitnessPlanOutput"}}}}}}},"/fitness/generate-plan-async":{"post":{"tags":["fitness"],"description":"Start fitness plan generation in the background. Returns a `jobId`; poll `/job-status/{jobId}` until `done` or `failed`.","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FitnessProfileInput"}}}},"responses":{"202":{"description":"Plan generation started in background","content":{"application/json":{"schema":{"type":"object","properties":{"jobId":{"type":"string"},"status":{"type":"string","enum":["pending","processing","done","failed"]}},"required":["jobId","status"]}}}}}}},"/fitness/exercises":{"get":{"tags":["fitness"],"description":"Paginated slice of the exercise catalog (exercise.json) for lazy lists and discovery UIs.","parameters":[{"schema":{"type":["number","null"],"minimum":0,"default":0},"required":false,"name":"offset","in":"query"},{"schema":{"type":"number","minimum":1,"maximum":100,"default":30},"required":false,"name":"limit","in":"query"}],"responses":{"200":{"description":"Paginated list of exercises (same shape as search)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExerciseSearchResponse"}}}}}}},"/fitness/exercises/batch":{"get":{"tags":["fitness"],"description":"Load multiple exercises by comma-separated IDs (e.g. for plan detail or homepage cards).","parameters":[{"schema":{"type":"string","description":"Comma-separated exercise IDs e.g. ids=1,2,3"},"required":true,"description":"Comma-separated exercise IDs e.g. ids=1,2,3","name":"ids","in":"query"}],"responses":{"200":{"description":"Exercises for the given IDs (same shape as search)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExerciseSearchResponse"}}}}}}},"/fitness/exercises/{id}":{"get":{"tags":["fitness"],"description":"Single exercise by ID, including demo/explanation video URLs. Used when expanding an exercise in a plan or sheet.","parameters":[{"schema":{"type":"string"},"required":true,"name":"id","in":"path"}],"responses":{"200":{"description":"Single exercise with video URLs (youtubeDemo, youtubeExplanation)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExerciseSearchItem"}}}},"404":{"description":"Exercise not found","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/fitness/job-status/{jobId}":{"get":{"tags":["fitness"],"description":"Poll status for an async fitness (or shared) plan job created via `/generate-plan-async`.","parameters":[{"schema":{"type":"string"},"required":true,"name":"jobId","in":"path"}],"responses":{"200":{"description":"Job status","content":{"application/json":{"schema":{"type":"object","properties":{"jobId":{"type":"string"},"status":{"type":"string","enum":["pending","processing","done","failed"]},"plan":{"type":"object","additionalProperties":{}},"error":{"type":["string","null"]}},"required":["jobId","status"]}}}},"404":{"description":"Job not found","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/fitness/exercises/search":{"post":{"tags":["fitness"],"description":"Semantic/text search over the public exercise catalog with optional filters (equipment, difficulty, muscle group).","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExerciseSearchRequest"}}}},"responses":{"200":{"description":"List of exercises (ids for use in build-plan-from-selections)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExerciseSearchResponse"}}}}}}},"/fitness/exercises/coach-instructions":{"post":{"tags":["fitness"],"description":"LLM-generated coaching summary, step list, and TTS script for one exercise before the user starts a set.","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"organizationId":{"type":"string","minLength":1},"userId":{"type":"string"},"exercise":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"description":{"type":"string"},"instructions":{"type":"array","items":{"type":"string"}},"sets":{"type":"number"},"repsPerSet":{"type":"number"},"durationSeconds":{"type":"number"},"restSeconds":{"type":"number"},"targetMuscles":{"type":"array","items":{"type":"string"}},"equipment":{"type":"array","items":{"type":"string"}},"difficulty":{"type":"string"},"type":{"type":"string"}},"required":["id","name"]},"catalogContext":{"type":["string","null"]}},"required":["organizationId","exercise"]}}}},"responses":{"200":{"description":"Coach instructions + TTS script","content":{"application/json":{"schema":{"type":"object","properties":{"summary":{"type":"string"},"steps":{"type":"array","items":{"type":"string"}},"ttsScript":{"type":"string"}},"required":["summary","steps","ttsScript"]}}}}}}},"/fitness/build-plan-from-selections":{"post":{"tags":["fitness"],"description":"Build a structured weekly plan from user-selected exercise IDs (build-your-own-plan flow).","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BuildPlanFromSelectionsRequest"}}}},"responses":{"200":{"description":"Plan built from selected exercises","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FitnessPlanOutput"}}}}}}},"/fitness/gym-log/insights":{"post":{"tags":["fitness"],"description":"LLM insights and optional structured blocks for a single gym log entry (post-workout reflection).","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"organizationId":{"type":"string","minLength":1},"userId":{"type":"string","description":"User ID"},"entry":{"type":"object","properties":{"id":{"type":"string"},"date":{"type":"string","description":"ISO date or date string"},"durationMinutes":{"type":"number"},"workoutType":{"type":"string"},"focus":{"type":"string"},"intensity":{"type":"string"},"notes":{"type":["string","null"]},"exercises":{"type":["array","null"],"items":{"type":"object","properties":{"name":{"type":"string"},"sets":{"type":"number"},"reps":{"type":"number"},"weightKg":{"type":"number"},"rpe":{"type":"number"},"notes":{"type":["string","null"]}},"required":["name"]}},"mood":{"type":["string","null"]},"energyLevel":{"type":["number","null"]},"sleepHoursLastNight":{"type":["number","null"]},"preWorkoutNutrition":{"type":["string","null"]},"postWorkoutNutrition":{"type":["string","null"]},"bodyWeightKg":{"type":["number","null"]},"personalRecords":{"type":["string","null"]},"injuriesOrPain":{"type":["string","null"]}},"required":["id","date","durationMinutes","workoutType","focus","intensity"]}},"required":["organizationId","userId","entry"]}}}},"responses":{"200":{"description":"Insights for the gym log entry","content":{"application/json":{"schema":{"type":"object","properties":{"insights":{"type":"string"},"blocks":{"type":"array","items":{"type":"object","properties":{"type":{"type":"string","enum":["title","card","text"]},"icon":{"type":"string"},"title":{"type":"string"},"body":{"type":"string"}},"required":["type","body"]}}},"required":["insights"]}}}}}}},"/fitness/admin/exercises":{"get":{"tags":["fitness"],"description":"Zwela-admin only: paginated exercise.json rows plus search-shaped `exercises`. Requires internal bearer or `X-Zwela-Internal-Secret`.","security":[{"ZwelaInternalBearer":[]},{"ZwelaInternalHeader":[]}],"parameters":[{"schema":{"type":["number","null"],"minimum":0,"default":0},"required":false,"name":"offset","in":"query"},{"schema":{"type":"number","minimum":1,"maximum":100,"default":50},"required":false,"name":"limit","in":"query"}],"responses":{"200":{"description":"Paginated list: rows = full exercise.json objects; exercises = API search shape","content":{"application/json":{"schema":{"type":"object","properties":{"exercises":{"type":"array","items":{"type":"object","additionalProperties":{}}},"rows":{"type":"array","items":{"type":"object","additionalProperties":{}}},"total":{"type":"number"},"offset":{"type":"number"},"limit":{"type":"number"}},"required":["exercises","rows","total","offset","limit"]}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"500":{"description":"Server error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Secret not configured","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}},"post":{"tags":["fitness"],"description":"Zwela-admin only: create a new exercise row, optionally cloning from `templateId` with field overrides in the body.","security":[{"ZwelaInternalBearer":[]},{"ZwelaInternalHeader":[]}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":{},"description":"templateId?: number, plus partial exercise.json fields"}}}},"responses":{"201":{"description":"Created row","content":{"application/json":{"schema":{"type":"object","properties":{"row":{"type":"object","additionalProperties":{}},"exercise":{"type":"object","additionalProperties":{}}},"required":["row","exercise"]}}}},"400":{"description":"Bad request","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Secret not configured","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/fitness/admin/exercises/{id}":{"get":{"tags":["fitness"],"description":"Zwela-admin only: fetch one raw exercise row and its API-shaped twin by numeric ID.","security":[{"ZwelaInternalBearer":[]},{"ZwelaInternalHeader":[]}],"parameters":[{"schema":{"type":"string"},"required":true,"name":"id","in":"path"}],"responses":{"200":{"description":"Single exercise row + search-shaped exercise","content":{"application/json":{"schema":{"type":"object","properties":{"row":{"type":"object","additionalProperties":{}},"exercise":{"type":"object","additionalProperties":{}}},"required":["row","exercise"]}}}},"400":{"description":"Invalid id","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"500":{"description":"Server error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Secret not configured","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}},"put":{"tags":["fitness"],"description":"Zwela-admin only: replace an exercise.json row by numeric ID (full document in JSON body).","security":[{"ZwelaInternalBearer":[]},{"ZwelaInternalHeader":[]}],"parameters":[{"schema":{"type":"string"},"required":true,"name":"id","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","additionalProperties":{},"description":"Full or partial exercise.json row"}}}},"responses":{"200":{"description":"Updated row","content":{"application/json":{"schema":{"type":"object","properties":{"row":{"type":"object","additionalProperties":{}},"exercise":{"type":"object","additionalProperties":{}}},"required":["row","exercise"]}}}},"400":{"description":"Bad request","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Secret not configured","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}},"delete":{"tags":["fitness"],"description":"Zwela-admin only: delete an exercise row by numeric ID from the catalog.","security":[{"ZwelaInternalBearer":[]},{"ZwelaInternalHeader":[]}],"parameters":[{"schema":{"type":"string"},"required":true,"name":"id","in":"path"}],"responses":{"200":{"description":"Deleted","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"id":{"type":"number"}},"required":["ok","id"]}}}},"400":{"description":"Bad request","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Secret not configured","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/friends/match":{"post":{"tags":["friends"],"description":"Return the top N users in the same org whose onboarding profile (interests, goals, health background) is most similar to the requesting user, ranked by cosine similarity of OpenAI onboarding embeddings. Embeddings are created on-demand and cached on the Firestore user document (onboardingEmbedding).","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"userId":{"type":"string","minLength":1},"orgId":{"type":"string","minLength":1},"limit":{"type":"integer","minimum":1,"maximum":50,"default":10}},"required":["userId","orgId"]}}}},"responses":{"200":{"description":"Matched user IDs sorted by onboarding similarity (highest first)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FriendsMatchResponse"}}}},"400":{"description":"Invalid body"},"500":{"description":"Matching failed"}}}},"/friends/embed-onboarding":{"post":{"tags":["friends"],"description":"Generate and persist an OpenAI embedding of a user's onboarding data in Firestore (organizations/{orgId}/users/{userId}.onboardingEmbedding). Call this once after onboarding completes so /friends/match is fast on first use. Idempotent — if the embedding already exists it is returned as-is (created: false).","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"userId":{"type":"string","minLength":1},"orgId":{"type":"string","minLength":1}},"required":["userId","orgId"]}}}},"responses":{"200":{"description":"Embedding created or already present","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmbedOnboardingResponse"}}}},"400":{"description":"Invalid body"},"500":{"description":"Embedding failed"}}}},"/image/search":{"get":{"tags":["general"],"description":"Proxy keyword search to Unsplash; returns a display URL (and optional thumb) for wellness UI image cards.","parameters":[{"schema":{"type":"string","minLength":1,"description":"Search keyword (e.g. running, mental health, mindfulness)"},"required":true,"description":"Search keyword (e.g. running, mental health, mindfulness)","name":"q","in":"query"},{"schema":{"type":"string","enum":["landscape","portrait","squarish"],"description":"Preferred orientation for cards"},"required":false,"description":"Preferred orientation for cards","name":"orientation","in":"query"}],"responses":{"200":{"description":"First matching image URL(s)","content":{"application/json":{"schema":{"type":"object","properties":{"url":{"type":"string","format":"uri","description":"Main image URL (Unsplash CDN, use for display)"},"thumb":{"type":"string","format":"uri","description":"Smaller URL for thumbnails"}},"required":["url"]}}}},"404":{"description":"No image found or Unsplash not configured"}}}},"/nutrition/diets":{"get":{"tags":["nutrition"],"description":"List static diet catalog entries; optional `q` filters by name, description, and tags.","parameters":[{"schema":{"type":"string","description":"Filter diets by substring match on name, description, tags"},"required":false,"description":"Filter diets by substring match on name, description, tags","name":"q","in":"query"}],"responses":{"200":{"description":"Diet catalog entries","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":{}}}}}},"500":{"description":"Server error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/nutrition/diets/{id}":{"get":{"tags":["nutrition"],"description":"Fetch one diet by `id` or URL `slug` from the catalog.","parameters":[{"schema":{"type":"string"},"required":true,"name":"id","in":"path"}],"responses":{"200":{"description":"Single diet","content":{"application/json":{"schema":{"type":"object","additionalProperties":{}}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"500":{"description":"Server error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/nutrition/kenyan-foods/search":{"get":{"tags":["nutrition"],"description":"Search the local Kenyan foods JSON catalogue; returns FatSecret-shaped objects with optional image enrichment.","parameters":[{"schema":{"type":"string","description":"Search query (local Kenyan catalogue)"},"required":false,"description":"Search query (local Kenyan catalogue)","name":"q","in":"query"},{"schema":{"type":"string"},"required":false,"name":"organizationId","in":"query"},{"schema":{"type":"number","minimum":1,"maximum":100,"default":50},"required":false,"name":"limit","in":"query"}],"responses":{"200":{"description":"Foods in FatSecret-like shape","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":{}}}}}},"500":{"description":"Server error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/nutrition/kenyan-meals/daily":{"get":{"tags":["nutrition"],"description":"App-facing daily meal bundle: reads Firestore `daily_bundles` cache and published blends (no full-catalog LLM). Use `refresh=1` to bypass cache.","parameters":[{"schema":{"type":"string"},"required":false,"name":"userId","in":"query"},{"schema":{"type":"string"},"required":false,"name":"date","in":"query"},{"schema":{"type":"string"},"required":false,"name":"organizationId","in":"query"},{"schema":{"type":"string","description":"Set to \"1\" to skip cache"},"required":false,"description":"Set to \"1\" to skip cache","name":"refresh","in":"query"},{"schema":{"type":"string","description":"Set to \"1\" to skip cache"},"required":false,"description":"Set to \"1\" to skip cache","name":"skipCache","in":"query"}],"responses":{"200":{"description":"Daily bundle (Firestore cache + published blends)","content":{"application/json":{"schema":{"type":"object","additionalProperties":{}}}}},"500":{"description":"Server error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/nutrition/admin/generate-daily":{"post":{"tags":["nutrition"],"description":"Zwela-admin only: run full-catalog LLM daily discovery, persist draft blends, and refresh caches. Requires internal secret; `organizationId` in body or query.","security":[{"ZwelaInternalBearer":[]},{"ZwelaInternalHeader":[]}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"organizationId":{"type":"string"},"userId":{"type":"string"},"date":{"type":"string"},"skipCache":{"type":"boolean"},"dietSlugs":{"type":"array","items":{"type":"string"}},"adminNotes":{"type":"string"},"briefingSummary":{"type":"string"},"fatsecretQueries":{"type":"array","items":{"type":"string"}},"syncCatalogFoods":{"type":"boolean"},"includeFatSecretInspiration":{"type":"boolean"}}}}}},"responses":{"200":{"description":"Generated daily bundle","content":{"application/json":{"schema":{"type":"object","additionalProperties":{}}}}},"400":{"description":"Bad request","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"500":{"description":"Server error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Secret not configured","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/nutrition/admin/generation-briefing":{"get":{"tags":["nutrition"],"description":"Zwela-admin only: pre-generation briefing (diets, Kenyan samples, FatSecret hits, Gemini copy). Supports repeated `fatsecretQuery` and `kenyanFoodId` query params.","security":[{"ZwelaInternalBearer":[]},{"ZwelaInternalHeader":[]}],"parameters":[{"schema":{"type":"string","description":"Comma-separated diet slugs"},"required":false,"description":"Comma-separated diet slugs","name":"dietSlugs","in":"query"},{"schema":{"type":"string","description":"Numeric seed"},"required":false,"description":"Numeric seed","name":"seed","in":"query"}],"responses":{"200":{"description":"Briefing payload for admin UI","content":{"application/json":{"schema":{"type":"object","additionalProperties":{}}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"500":{"description":"Server error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Secret not configured","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/nutrition/admin/sync-catalog-foods":{"post":{"tags":["nutrition"],"description":"Zwela-admin only: upsert all rows from `kenyan_foods.json` into Firestore `nutrition_library/kenyan/foods` for an org.","security":[{"ZwelaInternalBearer":[]},{"ZwelaInternalHeader":[]}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"organizationId":{"type":"string"}}}}}},"responses":{"200":{"description":"Sync result","content":{"application/json":{"schema":{"type":"object","additionalProperties":{}}}}},"400":{"description":"Bad request","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"500":{"description":"Server error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Secret not configured","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/nutrition/kenyan-meals/personalized-daily":{"get":{"tags":["nutrition"],"description":"Daily meal bundle from **published** Firestore blends only; Gemini matches slots to the user profile and recent logs (no full-catalog generation). `organizationId` required.","parameters":[{"schema":{"type":"string"},"required":false,"name":"userId","in":"query"},{"schema":{"type":"string"},"required":false,"name":"date","in":"query"},{"schema":{"type":"string","minLength":1,"description":"Required org id"},"required":true,"description":"Required org id","name":"organizationId","in":"query"}],"responses":{"200":{"description":"Personalized daily bundle","content":{"application/json":{"schema":{"type":"object","additionalProperties":{}}}}},"400":{"description":"Missing organizationId","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"500":{"description":"Server error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/nutrition/kenyan-meals/daily-partial":{"get":{"tags":["nutrition"],"description":"Fast library-only daily pack (no LLM). Pair with `/kenyan-meals/daily` when you need a full refresh.","parameters":[{"schema":{"type":"string"},"required":false,"name":"userId","in":"query"},{"schema":{"type":"string"},"required":false,"name":"date","in":"query"},{"schema":{"type":"string"},"required":false,"name":"organizationId","in":"query"}],"responses":{"200":{"description":"Library-only daily pack","content":{"application/json":{"schema":{"type":"object","additionalProperties":{}}}}},"500":{"description":"Server error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/nutrition/kenyan-recipes/library":{"get":{"tags":["nutrition"],"description":"Recipe cards for nutrition lists: FatSecret-backed variety with `limit`, `seed`, and optional `dayName` for theming.","parameters":[{"schema":{"type":"number","minimum":1,"maximum":100,"default":40},"required":false,"name":"limit","in":"query"},{"schema":{"type":["number","null"],"default":0},"required":false,"name":"seed","in":"query"},{"schema":{"type":"string"},"required":false,"name":"dayName","in":"query"}],"responses":{"200":{"description":"Recipe cards","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":{}}}}}},"500":{"description":"Server error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/nutrition/kenyan-meals/blends":{"get":{"tags":["nutrition"],"description":"Suggest Kenyan blend plates for feeds and discovery (`count`, `seed`, optional `dayName` and `organizationId`).","parameters":[{"schema":{"type":"number","minimum":1,"maximum":20,"default":6},"required":false,"name":"count","in":"query"},{"schema":{"type":["number","null"],"default":0},"required":false,"name":"seed","in":"query"},{"schema":{"type":"string"},"required":false,"name":"dayName","in":"query"},{"schema":{"type":"string"},"required":false,"name":"organizationId","in":"query"}],"responses":{"200":{"description":"Suggested blend plates","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":{}}}}}},"500":{"description":"Server error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/nutrition/foods/discovery-category-searches":{"get":{"tags":["nutrition"],"description":"Per user per UTC day: personalized FatSecret search phrases for discovery chips (Gemini + Firestore cache). Requires organizationId.","parameters":[{"schema":{"type":"string"},"required":false,"name":"userId","in":"query"},{"schema":{"type":"string"},"required":false,"name":"date","in":"query"},{"schema":{"type":"string","minLength":1,"description":"Required org id"},"required":true,"description":"Required org id","name":"organizationId","in":"query"}],"responses":{"200":{"description":"Map of category slug → search query","content":{"application/json":{"schema":{"type":"object","additionalProperties":{}}}}},"400":{"description":"Missing organizationId","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"500":{"description":"Server error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/nutrition/foods/discovery-category-feed":{"get":{"tags":["nutrition"],"description":"Per category: LLM-cached search phrase lists (per user/day), each phrase searched on FatSecret, merged + deduped. Query: slug (fibre|iron|…), userId, organizationId.","parameters":[{"schema":{"type":"string","minLength":1,"description":"Category slug e.g. fibre, iron, protein"},"required":true,"description":"Category slug e.g. fibre, iron, protein","name":"slug","in":"query"},{"schema":{"type":"string"},"required":false,"name":"userId","in":"query"},{"schema":{"type":"string"},"required":false,"name":"date","in":"query"},{"schema":{"type":"string","minLength":1,"description":"Required org id"},"required":true,"description":"Required org id","name":"organizationId","in":"query"}],"responses":{"200":{"description":"Merged foods + metadata","content":{"application/json":{"schema":{"type":"object","additionalProperties":{}}}}},"400":{"description":"Bad slug or missing organizationId","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"500":{"description":"Server error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/nutrition/foods/search":{"get":{"tags":["nutrition"],"description":"Proxy search against FatSecret foods; results are image-enriched for app display.","parameters":[{"schema":{"type":"string"},"required":false,"name":"q","in":"query"}],"responses":{"200":{"description":"FatSecret food search hits","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":{}}}}}},"500":{"description":"Server error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/nutrition/recipes/search":{"get":{"tags":["nutrition"],"description":"Proxy search against FatSecret recipes with image enrichment.","parameters":[{"schema":{"type":"string"},"required":false,"name":"q","in":"query"}],"responses":{"200":{"description":"FatSecret recipe search hits","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":{}}}}}},"500":{"description":"Server error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/nutrition/foods/{foodId}":{"get":{"tags":["nutrition"],"description":"Food detail by ID: supports FatSecret IDs, `kenyan_*` catalogue rows, and `kenyan_blend_*` composite plates.","parameters":[{"schema":{"type":"string"},"required":true,"name":"foodId","in":"path"},{"schema":{"type":"string"},"required":false,"name":"organizationId","in":"query"}],"responses":{"200":{"description":"Food detail (FatSecret or Kenyan)","content":{"application/json":{"schema":{"type":"object","additionalProperties":{}}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"500":{"description":"Server error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/nutrition/recipes/{recipeId}":{"get":{"tags":["nutrition"],"description":"Recipe detail by ID: FatSecret recipes or `kenyan_blend_*` converted to a recipe-shaped payload.","parameters":[{"schema":{"type":"string"},"required":true,"name":"recipeId","in":"path"}],"responses":{"200":{"description":"Recipe detail","content":{"application/json":{"schema":{"type":"object","additionalProperties":{}}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"500":{"description":"Server error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/nutrition/suggest-ingredients":{"post":{"tags":["nutrition"],"description":"LLM-suggested ingredients given what the user already picked; optional `dietName` and `recommendedOnly`.","requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"currentIngredients":{"type":"array","items":{"type":"string"}},"dietName":{"type":"string"},"recommendedOnly":{"type":"boolean"}}}}}},"responses":{"200":{"description":"Suggested ingredient strings","content":{"application/json":{"schema":{"type":"object","properties":{"ingredients":{"type":"array","items":{"type":"string"}}},"required":["ingredients"]}}}},"500":{"description":"Server error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/nutrition/suggest-meals":{"post":{"tags":["nutrition"],"description":"Suggest meals for a day from optional diet, ingredient list, day name, and org context (build-plan / planner flows).","requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"dietName":{"type":"string"},"ingredients":{"type":"array","items":{"type":"string"}},"dayName":{"type":"string"},"limit":{"type":"number"},"organizationId":{"type":"string"}}}}}},"responses":{"200":{"description":"Meal suggestions","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":{}}}}}},"500":{"description":"Server error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/nutrition/generate-plan":{"post":{"tags":["nutrition"],"description":"Generate a structured multi-stage nutrition plan synchronously from the profile (LLM). Prefer `/generate-plan-async` if the client might time out.","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/NutritionProfileInput"}}}},"responses":{"200":{"description":"Successfully generated nutrition plan","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NutritionPlanOutput"}}}}}}},"/nutrition/generate-plan-async":{"post":{"tags":["nutrition"],"description":"Start async nutrition plan generation; returns `jobId`. Poll `/nutrition/job-status/{jobId}` (same shared job store as fitness jobs).","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/NutritionProfileInput"}}}},"responses":{"202":{"description":"Plan generation started in background","content":{"application/json":{"schema":{"type":"object","properties":{"jobId":{"type":"string"},"status":{"type":"string","enum":["pending","processing","done","failed"]}},"required":["jobId","status"]}}}}}}},"/nutrition/job-status/{jobId}":{"get":{"tags":["nutrition"],"description":"Poll async nutrition plan job status. **Note:** job IDs are shared in-process with fitness async jobs—disambiguate by which endpoint created the job.","parameters":[{"schema":{"type":"string"},"required":true,"name":"jobId","in":"path"}],"responses":{"200":{"description":"Job status","content":{"application/json":{"schema":{"type":"object","properties":{"jobId":{"type":"string"},"status":{"type":"string","enum":["pending","processing","done","failed"]},"plan":{"type":"object","additionalProperties":{}},"error":{"type":["string","null"]}},"required":["jobId","status"]}}}},"404":{"description":"Job not found","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/wellsphere/condition":{"post":{"tags":["wellsphere"],"description":"Look up chronic condition education and guidance for the Wellsphere Flutter flow (snake_case JSON).","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConditionQueryInput"}}}},"responses":{"200":{"description":"Successfully fetched chronic condition information (snake_case for Flutter)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConditionFlutterResponse"}}}}}}},"/wellsphere/drug":{"post":{"tags":["wellsphere"],"description":"Medicine / drug information query for Wellsphere (snake_case JSON for Flutter).","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MedicineQueryInput"}}}},"responses":{"200":{"description":"Successfully fetched drug information (snake_case for Flutter)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DrugFlutterResponse"}}}}}}},"/wellsphere/analyze-metric":{"post":{"tags":["wellsphere"],"description":"Analyze a single health metric reading in context (structured input → structured insight).","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MetricAnalysisInput"}}}},"responses":{"200":{"description":"Successfully analyzed health metric","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MetricAnalysisOutput"}}}}}}},"/wellsphere/analyze-passport":{"post":{"tags":["wellsphere"],"description":"Summarize or analyze a set of passport / longitudinal health metrics for the user.","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PassportAnalysisInput"}}}},"responses":{"200":{"description":"Successfully analyzed passport metrics","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PassportAnalysisOutput"}}}}}}},"/wellsphere/daily-hub":{"post":{"tags":["wellsphere"],"description":"Generate or return cached daily Wellsphere home content (PubMed-backed). Stored under organizations/{orgId}/wellsphere/{userId}/daily_hub/{date}.","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WellsphereDailyHubInput"}}}},"responses":{"200":{"description":"Daily hub payload (snake_case fields for Flutter)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WellsphereDailyHubResponse"}}}}}}},"/wellsphere/management-plan":{"post":{"tags":["wellsphere"],"description":"Generate or return cached monthly Wellsphere management plan: long-form PubMed-grounded Markdown (~4k+ words), five calendar-week checkpoints with 10–15 Likert questions each. Stored under organizations/{orgId}/wellsphere/{userId}/management_plans/{yyyy-MM}.","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WellsphereManagementPlanInput"}}}},"responses":{"200":{"description":"Management plan payload (snake_case fields for Flutter)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WellsphereManagementPlanResponse"}}}}}}},"/wellsphere/symptoms/suggest":{"post":{"tags":["wellsphere"],"description":"Suggest symptom labels and education for the user's condition using PubMed-grounded LangGraph. Cached ~12h under wellsphere/{userId}/symptom_suggestions/current.","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WellsphereSymptomSuggestInput"}}}},"responses":{"200":{"description":"Suggested symptoms + sources (snake_case)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WellsphereSymptomSuggestResponse"}}}}}}},"/wellness/pubmed":{"get":{"tags":["wellness"],"description":"Search PubMed via multiple short esearch queries (client `q` chunks + profile interests + daily focus); merges PMIDs. App may filter results.","parameters":[{"schema":{"type":"string","default":"wellness preventive health lifestyle"},"required":false,"name":"q","in":"query"},{"schema":{"type":"number","minimum":1,"maximum":20,"default":5},"required":false,"name":"max","in":"query"},{"schema":{"type":"string","minLength":1},"required":true,"name":"userId","in":"query"}],"responses":{"200":{"description":"PubMed articles (title, excerpt, sourceUrl, pmid)","content":{"application/json":{"schema":{"type":"object","properties":{"articles":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"},"excerpt":{"type":"string"},"sourceLabel":{"type":"string"},"sourceUrl":{"type":"string"},"publishedDate":{"type":["string","null"]},"pmid":{"type":"string"},"imageUrl":{"type":["string","null"]},"imageKeywords":{"type":"string"}},"required":["id","title","excerpt","sourceLabel","sourceUrl","publishedDate","pmid"]}},"error":{"type":"string"}},"required":["articles"]}}}},"502":{"description":"PubMed/NCBI failed or returned no usable articles (no placeholder content)","content":{"application/json":{"schema":{"type":"object","properties":{"articles":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"},"excerpt":{"type":"string"},"sourceLabel":{"type":"string"},"sourceUrl":{"type":"string"},"publishedDate":{"type":["string","null"]},"pmid":{"type":"string"},"imageUrl":{"type":["string","null"]},"imageKeywords":{"type":"string"}},"required":["id","title","excerpt","sourceLabel","sourceUrl","publishedDate","pmid"]}},"error":{"type":"string"}},"required":["articles"]}}}}}}},"/wellness/pubmed/summarize":{"post":{"tags":["wellness"],"description":"Get or create an LLM Markdown summary for a PubMed article (`pmid`), cached per organization in Firestore.","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"pmid":{"type":"string"},"organizationId":{"type":"string","minLength":1},"title":{"type":"string"},"excerpt":{"type":"string"},"sourceUrl":{"type":"string"},"sourceLabel":{"type":"string"}},"required":["pmid","organizationId"]}}}},"responses":{"200":{"description":"Markdown summary (from cache or freshly generated)","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","enum":[true]},"cached":{"type":"boolean"},"pmid":{"type":"string"},"summaryMarkdown":{"type":"string"},"title":{"type":"string"},"sourceUrl":{"type":"string"},"sourceLabel":{"type":"string"}},"required":["ok","cached","pmid","summaryMarkdown","title","sourceUrl","sourceLabel"]}}}},"400":{"description":"Invalid body"},"500":{"description":"Summarization failed"}}}},"/wellness/hidden-gem/discover":{"post":{"tags":["wellness"],"description":"LLM proposes a local place query; Google Places returns one “hidden gem” result for the user (requires Places API keys).","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"userId":{"type":"string","minLength":1},"country":{"type":"string"},"city":{"type":"string"},"interestsOrMood":{"type":"string"}},"required":["userId"]}}}},"responses":{"200":{"description":"One hidden gem place (or empty object if none found)","content":{"application/json":{"schema":{"type":"object","properties":{"placeId":{"type":"string"},"name":{"type":["string","null"]},"description":{"type":["string","null"]},"imageUrl":{"type":["string","null"]},"imageUrls":{"type":["array","null"],"items":{"type":"string"}},"rating":{"type":["number","null"]},"address":{"type":["string","null"]},"vicinity":{"type":["string","null"]},"priceLevel":{"type":["number","null"]},"openingHoursText":{"type":["string","null"]},"lat":{"type":["number","null"]},"lng":{"type":["number","null"]},"category":{"type":["string","null"]}},"required":["name","description","imageUrl","rating","address","vicinity","priceLevel","openingHoursText","lat","lng","category"]}}}},"503":{"description":"Places API not configured"}}}},"/wellness/daily/hook":{"post":{"tags":["wellness"],"description":"One short LLM hook line for “of the day” cards (exercise, food, or recipe) given title/subtitle and `userId`.","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"type":{"type":"string","enum":["exercise","food","recipe"]},"title":{"type":"string","minLength":1,"maxLength":200},"subtitle":{"type":["string","null"],"maxLength":500},"userId":{"type":"string","minLength":1}},"required":["type","title","userId"]}}}},"responses":{"200":{"description":"One short LLM-generated hook for the of-the-day card","content":{"application/json":{"schema":{"type":"object","properties":{"hook":{"type":"string"}},"required":["hook"]}}}},"400":{"description":"Invalid body"}}}},"/wellness/daily/titles":{"get":{"tags":["wellness"],"description":"Curated homepage section titles for a calendar day (Gemini), generated once per user per day and cached.","parameters":[{"schema":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$"},"required":false,"name":"date","in":"query"},{"schema":{"type":"string","minLength":1},"required":true,"name":"userId","in":"query"}],"responses":{"200":{"description":"Curated section titles for the day (generated once per user per day, cached per user)","content":{"application/json":{"schema":{"type":"object","properties":{"titles":{"type":"object","additionalProperties":{"type":"string"}},"date":{"type":"string"},"cached":{"type":"boolean"}},"required":["titles","date","cached"]}}}}}}},"/wellness/daily/mindspace-prompts":{"get":{"tags":["wellness"],"description":"Daily journal and gratitude prompts (Gemini), one set per user per day with server-side caching.","parameters":[{"schema":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$"},"required":false,"name":"date","in":"query"},{"schema":{"type":"string","minLength":1},"required":true,"name":"userId","in":"query"}],"responses":{"200":{"description":"Daily journal and gratitude prompts (generated once per user per day, cached per user)","content":{"application/json":{"schema":{"type":"object","properties":{"journalPrompt":{"type":"string"},"gratitudePrompt":{"type":"string"},"date":{"type":"string"},"cached":{"type":"boolean"}},"required":["journalPrompt","gratitudePrompt","date","cached"]}}}}}}},"/wellness/daily/mindspace-prompts/extra":{"post":{"tags":["wellness"],"description":"One-off extra journal or gratitude prompt for “do another” flows (not cached; optional `requestNonce` for variety).","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"type":{"type":"string","enum":["journal","gratitude"]},"userId":{"type":"string","minLength":1},"requestNonce":{"type":"string","minLength":1}},"required":["type","userId"]}}}},"responses":{"200":{"description":"One extra journal or gratitude prompt (not cached)","content":{"application/json":{"schema":{"type":"object","properties":{"prompt":{"type":"string"}},"required":["prompt"]}}}},"400":{"description":"Invalid body"}}}},"/scan/food":{"post":{"tags":["scan"],"description":"Vision model: estimate food name, macros, and vitality score from a base64 meal photo.","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FoodScanInput"}}}},"responses":{"200":{"description":"Successfully scanned food image","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FoodScanOutput"}}}}}}},"/scan/prescription":{"post":{"tags":["scan"],"description":"Vision model: extract structured fields from a prescription image (for pharmacy / adherence flows).","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PrescriptionScanInput"}}}},"responses":{"200":{"description":"Successfully scanned prescription image","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PrescriptionScanOutput"}}}}}}},"/scan/receipt":{"post":{"tags":["scan"],"description":"Vision model: parse a receipt image into line items / totals (wellness spend tracking).","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReceiptScanInput"}}}},"responses":{"200":{"description":"Successfully scanned receipt image","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReceiptScanOutput"}}}}}}},"/chat/message":{"post":{"tags":["chat"],"description":"Zwela chat turn: LangGraph agent with optional history, attachments, and org/user context; may suggest in-app navigation.","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LangChatInput"}}}},"responses":{"200":{"description":"Successfully processed chat message","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LangChatOutput"}}}}}}},"/connect/agora/rtc-token":{"post":{"tags":["connect"],"description":"Mint a short-lived Agora RTC token for the signed-in user to join a Connect call. Requires Firebase Bearer auth. For 1:1 calls, user must be `callerId` or `receiverId`. For group calls (`groupId` on the call doc), caller is always allowed; others must be `active` members of `organizations/{orgId}/chats/{groupId}`. Env: `AGORA_APP_ID`, `AGORA_APP_CERTIFICATE` (optional `AGORA_RTC_TOKEN_TTL_SECONDS`).","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"orgId":{"type":"string","minLength":1},"callId":{"type":"string","minLength":1}},"required":["orgId","callId"]}}}},"responses":{"200":{"description":"RTC token for Agora joinChannel","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnectAgoraRtcTokenResponse"}}}},"400":{"description":"Invalid body"},"401":{"description":"Missing or invalid Firebase token"},"403":{"description":"User is not a participant on this call"},"404":{"description":"Call document not found"},"500":{"description":"Token minting failed"},"503":{"description":"Agora or Firestore not configured"}}}}},"webhooks":{}}