Authenticated meal analysis, food logging, nutrition plans, and nutrition chat.
AI Nutrition provides meal analysis, food logging, nutrition planning, and a nutrition assistant for authenticated users. Requests are client-scoped by the request hostname and require the AI Nutrition module and the client's food-AI feature entitlement.
Send a Firebase ID token in the Authorization header:
Authorization: Bearer <firebase-id-token>The endpoints operate on the authenticated user's records. Do not send a
clientId or another user's ID to select data.
/api/ai-nutrition/plansReturns the authenticated user's most recent nutrition plans (up to 10).
{
"plans": [
{
"planId": "plan-001",
"clientId": "gofa",
"userId": "user-001",
"goal": "LOSE",
"dailyCalories": 2000,
"macros": {
"protein": 150,
"carbs": 200,
"fats": 65
},
"targetWeight": "70",
"bmi": "22.5",
"explanation": "Based on your profile, a moderate calorie deficit...",
"foodSuggestions": [
{ "name": "Grilled chicken breast", "reason": "High protein, low fat" },
{ "name": "Brown rice", "reason": "Complex carbs for sustained energy" }
],
"isActive": true,
"createdAt": "2026-03-17T10:00:00Z"
}
]
}/api/ai-nutrition/generate-planGenerates and stores a personalized daily plan. A new plan replaces the previous active plan for the user.
| Field | Type | Required | Description |
|---|---|---|---|
goal | "LOSE" | "MAINTAIN" | "GAIN" | Yes | Nutrition goal. |
targetWeight | string | No | Target weight supplied by the user. |
exerciseFrequency | string | No | How often the user exercises. |
dietaryStaple | string | No | Primary dietary staple. |
bodyFrame | string | No | Body-frame description. |
healthFocus | string | No | Health focus area. |
{
"goal": "LOSE",
"targetWeight": "70",
"exerciseFrequency": "3-4 times per week",
"dietaryStaple": "rice",
"healthFocus": "heart health"
}The successful response contains the generated plan fields directly:
{
"planId": "plan-002",
"dailyCalories": 1800,
"macros": {
"protein": 135,
"carbs": 180,
"fats": 60
},
"bmi": "24.1",
"explanation": "Your personalized plan focuses on...",
"foodSuggestions": [
{ "name": "Salmon", "reason": "Rich in omega-3 for heart health" },
{ "name": "Oats", "reason": "High fiber to support weight management" }
]
}/api/ai-nutrition/analyze-mealAnalyzes a food image and stores the resulting nutrition analysis.
| Field | Type | Required | Description |
|---|---|---|---|
imageBase64 | string | Yes | Raw base64 data or a data:image/...;base64,... value. |
language | "en" | "zh-Hant" | "zh-Hans" | No | User-visible response language; defaults to "en". |
JPEG, PNG, and WebP images are accepted. The decoded image must be no larger than 350 KB for this endpoint. Photos are screened for personal identity information before analysis; a face, identifiable person, identity document, personal screen, or clearly readable personal identifier can be rejected.
{
"imageBase64": "<base64-encoded-image>",
"language": "en"
}{
"analysisId": "analysis-001",
"text": "This appears to be a grilled chicken salad with mixed greens, providing a good balance of protein and fiber.",
"foodData": {
"name": "Grilled Chicken Salad",
"dishName": "Grilled Chicken Salad",
"cuisineType": "Western",
"visibleIngredients": ["chicken", "mixed greens"],
"portionLabel": "regular",
"portionConfidence": "medium",
"estimationBasis": "Estimated from dish identification and visible portion cues.",
"disclaimer": "Nutrition is estimated from the identified dish and visual portion cues.",
"calories": 350,
"protein": 30,
"carbs": 15,
"fats": 18,
"sugar": 4,
"mealGrade": "B",
"localizedText": {
"en": {
"name": "Grilled Chicken Salad",
"aiInsights": "A balanced, protein-forward meal."
}
}
},
"createdAt": "2026-03-17T12:30:00.000Z"
}The numeric nutrition values are estimates. mealGrade is A, B, or C
under the service's healthy-diet grading rules. healthScore may appear on
older records and remains accepted for compatibility, but new clients should
use mealGrade.
Privacy rejection returns 422:
{
"error": "This photo may contain personal identity information. Please retake the photo so it shows only the food.",
"code": "PERSONAL_IDENTITY_DETECTED"
}Invalid image media types return 415; invalid or empty image data returns
400; an image over the endpoint limit returns 413.
/api/ai-nutrition/food-logsCreates a food log for the authenticated user. The server assigns the current Hong Kong calendar date; the request does not choose a different date.
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Food name. |
dishName | string | No | More specific dish name. |
cuisineType | string | No | Cuisine or regional context. |
visibleIngredients | string[] | No | Ingredients identified by the caller or scan. |
portionLabel | "small" | "regular" | "large" | "extra_large" | "unknown" | No | Portion estimate. |
portionConfidence | "low" | "medium" | "high" | No | Portion confidence. |
estimationBasis | string | No | Basis for estimated values. |
disclaimer | string | No | Nutrition estimate disclaimer. |
calories | number | Yes | Calories, minimum 0. |
protein | number | Yes | Protein in grams, minimum 0. |
carbs | number | Yes | Carbohydrates in grams, minimum 0. |
fats | number | Yes | Fat in grams, minimum 0. |
sugar | number | No | Sugar in grams, minimum 0. |
mealGrade | "A" | "B" | "C" | No | Healthy-diet grade. |
healthScore | number | No | Deprecated 0–100 compatibility field. |
localizedText | object | No | Localized food strings keyed by supported locale. |
imageUrl | string | No | Existing image URL. |
imagePath | string | No | Existing image storage path. |
source | "scan" | "manual" | "chat" | No | Log source; defaults to "manual". |
analysisId | string | No | Link to a meal analysis. |
{
"name": "Grilled Chicken Salad",
"calories": 350,
"protein": 30,
"carbs": 15,
"fats": 18,
"source": "scan",
"analysisId": "analysis-001"
}{
"logId": "log-001",
"clientId": "gofa",
"userId": "user-001",
"name": "Grilled Chicken Salad",
"calories": 350,
"protein": 30,
"carbs": 15,
"fats": 18,
"source": "scan",
"analysisId": "analysis-001",
"date": "2026-03-17",
"createdAt": "2026-03-17T12:35:00.000Z"
}/api/ai-nutrition/food-logsGets logs for a date or recent history for the authenticated user.
| Query parameter | Type | Description |
|---|---|---|
date | YYYY-MM-DD | Return logs for that date. Defaults to today in Hong Kong time. |
limit | integer | When supplied without date, return recent history, clamped to 1–100; the default is 30. |
{
"date": "2026-03-17",
"limit": null,
"logs": [
{
"logId": "log-001",
"clientId": "gofa",
"userId": "user-001",
"name": "Grilled Chicken Salad",
"calories": 350,
"protein": 30,
"carbs": 15,
"fats": 18,
"source": "scan",
"date": "2026-03-17",
"createdAt": "2026-03-17T12:35:00.000Z"
}
]
}When limit is supplied without date, the response has date: null, the
effective clamped limit, and the most recent logs. If a log references an
analysis, its available image URL is also resolved in the response.
/api/ai-nutrition/food-logs/[logId]Gets one food log owned by the authenticated user. If analysisId is present
and the analysis is available, the response also includes the linked analysis.
{
"log": {
"logId": "log-001",
"clientId": "gofa",
"userId": "user-001",
"name": "Grilled Chicken Salad",
"calories": 350,
"protein": 30,
"carbs": 15,
"fats": 18,
"source": "scan",
"analysisId": "analysis-001",
"imageUrl": null,
"date": "2026-03-17",
"createdAt": "2026-03-17T12:35:00.000Z"
},
"analysis": {
"analysisId": "analysis-001",
"clientId": "gofa",
"userId": "user-001",
"imageUrl": null,
"foodData": {
"name": "Grilled Chicken Salad",
"calories": 350,
"protein": 30,
"carbs": 15,
"fats": 18
},
"aiInsights": "A balanced, protein-forward meal.",
"logged": true,
"createdAt": "2026-03-17T12:30:00.000Z"
}
}/api/ai-nutrition/food-logs/[logId]Deletes a food log owned by the authenticated user.
{
"success": true
}The route returns 403 for a log owned by another user and 404 when the log
does not exist.
/api/ai-nutrition/chatSends a message to the authenticated user's nutrition assistant. The response is JSON and the conversation is persisted server-side.
| Field | Type | Required | Description |
|---|---|---|---|
message | string | Yes | User message, at least one character. |
imageBase64 | string | null | No | Optional JPEG, PNG, or WebP image. |
conversationId | string | No | Existing conversation to continue. |
language | "en" | "zh-Hant" | "zh-Hans" | No | Response language; defaults to "en". |
{
"message": "I just had a banana and a glass of milk for breakfast",
"conversationId": "conv-001",
"language": "en"
}{
"conversationId": "conv-001",
"response": "A banana with milk is a useful quick breakfast. The banana provides potassium and fiber, while the milk adds protein and calcium.",
"suggestedFoodLog": {
"name": "Banana and Milk",
"calories": 250,
"protein": 10,
"carbs": 40,
"fats": 5,
"sugar": 28,
"mealGrade": "B"
}
}suggestedFoodLog is nullable. When present, the client can show it for
confirmation and submit the accepted fields to
POST /api/ai-nutrition/food-logs. The assistant response and food strings
follow the requested language.
Images attached to chat are screened for personal identity information. A
rejected image returns 422 with the same PERSONAL_IDENTITY_DETECTED code
shown in the meal-analysis section. Invalid image data or unsupported media
types return the corresponding 400 or 415 validation response.
This page documents user-facing plans, meal analysis, food logs, and nutrition chat. Admin record queries, module settings, media configuration, and other B2B/operator endpoints are outside the public integration contract.