Base URL during local development: http://localhost:3000
This document covers the application API exposed by the backend in server/src/routes/.
- Content type for most endpoints:
application/json - Upload endpoints use
multipart/form-data - Auth-protected endpoints require a valid Better Auth session
- Uploaded files are served from
/uploads/* - Most successful responses use
success: true - Validation and server errors use
success: false
Auth routes are mounted at:
ALL /api/auth/*
These are provided by Better Auth via auth.ts, so the exact auth sub-routes are managed by that library rather than handwritten route files in this repo.
For app-owned protected endpoints, the backend checks the incoming session using request headers. In practice, frontend clients should send the Better Auth session cookie and/or the Authorization header used by their auth flow.
Most errors return:
{
"success": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "Validation failed",
"errors": {
"fieldName": ["Error message"]
}
}
}Common codes:
UNAUTHORIZEDVALIDATION_ERRORNOT_FOUNDIMAGE_REQUIREDAUDIO_REQUIREDINVALID_FILE_TYPEINVALID_AUDIO_TYPEAI_SERVICE_ERROR-style failures wrapped through the app error layer
Health check for the server.
Auth: not required
Response:
{
"status": "OK",
"timestamp": "2026-03-22T00:00:00.000Z"
}Queues a sample text log job.
Auth: not required
Request body:
{
"text": "Had a protein shake after a workout"
}Response:
{
"status": "queued",
"message": "Job added to queue"
}Runs a lightweight embedding + AI response check.
Auth: not required
Request body:
{
"text": "Breakfast was oats and berries"
}Response:
{
"status": "success",
"vectorSize": 3072,
"aiResponse": "Short summary text"
}Creates a text health log and queues it for background processing.
Auth: required
Request body:
{
"content": "Had oatmeal for breakfast and felt good afterward"
}Validation:
content: required non-empty string
Behavior:
- stores a
log_entryrow withpendingstatus - enqueues a BullMQ job
- worker generates embeddings, extracts facts, and updates the daily summary
Response:
{
"success": true,
"id": "log-uuid",
"status": "queued",
"message": "Log saved and processing started"
}Asks a grounded health/nutrition question using stored facts, daily summaries, and previous logs.
Auth: required
Request body:
{
"message": "What patterns are showing up in my meals this week?"
}Validation:
message: required non-empty string
Response:
{
"success": true,
"answer": "Your protein has been strongest at breakfast...",
"debug": {
"factsUsed": 4,
"logsUsed": 5,
"hasSummary": true,
"verification": "Enabled"
}
}Food supports both photo-driven and manual entry.
Uploads a meal image for AI analysis.
Auth: required
Content type: multipart/form-data
Form fields:
image: required filemealType: optional, one ofbreakfast | lunch | dinner | snacknotes: optional string, max 500 chars
Accepted file types:
image/jpegimage/pngimage/webp
File size limit:
- 10 MB
Behavior:
- stores the image in
server/uploads/food - creates a pending
food_log - enqueues food analysis
- worker updates nutrition totals, revision history, vector memory, and daily nutrition summary
Response:
{
"success": true,
"id": "food-log-uuid",
"status": "queued",
"imageUrl": "/uploads/food/1711111111111-meal.jpg"
}Creates a completed meal without image analysis.
Auth: required
Request body:
{
"title": "Chicken rice bowl",
"mealType": "lunch",
"notes": "Homemade",
"loggedAt": "2026-03-22T12:30:00.000Z",
"detectedFoods": [
{
"name": "chicken breast",
"estimatedPortion": "150g",
"calories": 250,
"protein": 35,
"carbs": 0,
"fat": 6,
"confidence": null
},
{
"name": "rice",
"estimatedPortion": "1 cup",
"calories": 270,
"protein": 5,
"carbs": 45,
"fat": 2,
"confidence": null
}
],
"totalCalories": 520,
"totalProtein": 40,
"totalCarbs": 45,
"totalFat": 12
}Validation:
title: optional, max 120 charsmealType: optional enumnotes: optional, max 500 charsloggedAt: optional datedetectedFoods: required array with at least one item
Response:
{
"success": true,
"data": {
"id": "food-log-uuid",
"userId": "user-uuid",
"title": "Chicken rice bowl",
"entryMode": "manual",
"status": "completed"
}
}Lists all food logs for the authenticated user, newest first.
Auth: required
Response:
{
"success": true,
"data": [
{
"id": "food-log-uuid",
"entryMode": "photo",
"mealType": "dinner",
"status": "completed",
"totalCalories": 640
}
]
}Returns one food log.
Auth: required
Path params:
id: food log id
Response:
{
"success": true,
"data": {
"id": "food-log-uuid",
"entryMode": "hybrid",
"detectedFoods": [],
"correctedData": {},
"status": "completed"
}
}Returns revision history for a food log.
Auth: required
Path params:
id: food log id
Revision types currently used:
ai_initialmanual_initialuser_editreprocessreserved for future use
Response:
{
"success": true,
"data": [
{
"id": "revision-uuid",
"foodLogId": "food-log-uuid",
"revisionType": "ai_initial",
"data": {
"detectedFoods": [],
"totalCalories": 600,
"totalProtein": 30,
"totalCarbs": 55,
"totalFat": 20,
"title": null,
"mealType": "dinner",
"notes": "Restaurant meal",
"entryMode": "photo"
},
"createdAt": "2026-03-22T12:00:00.000Z"
}
]
}Updates a food log. This is mainly used for user corrections after AI analysis.
Auth: required
Path params:
id: food log id
Request body:
{
"title": "Updated meal title",
"mealType": "dinner",
"notes": "Adjusted after reviewing",
"correctedData": {
"detectedFoods": [
{
"name": "paneer curry",
"estimatedPortion": "1 bowl",
"calories": 420,
"protein": 22,
"carbs": 18,
"fat": 28,
"confidence": null
}
],
"totalCalories": 420,
"totalProtein": 22,
"totalCarbs": 18,
"totalFat": 28,
"notes": "User-corrected"
}
}Validation:
title: optional, max 120 charsmealType: optional enumnotes: optional, max 500 charscorrectedData: optional object; if provided,detectedFoodsmust contain at least one item
Behavior:
- saves corrected values to the
food_log - creates a
user_editrevision ifcorrectedDatais present - switches
entryModefromphototohybridwhen appropriate - refreshes daily nutrition summary
- re-syncs vector memory when correction data is provided
Response:
{
"success": true,
"data": {
"id": "food-log-uuid",
"entryMode": "hybrid",
"userCorrected": true
}
}Deletes a food log and its revision history.
Auth: required
Path params:
id: food log id
Behavior:
- deletes DB row
- deletes related revisions
- deletes stored image file if present
- removes vector point
- refreshes daily nutrition summary
Response:
{
"success": true,
"message": "Food log deleted"
}Voice notes are converted into normal text log_entry records and then processed through the same log enrichment pipeline.
Uploads an audio note for transcription.
Auth: required
Content type: multipart/form-data
Form fields:
audio: required filedurationSeconds: optional positive integer, max1800
Accepted audio types:
audio/mpegaudio/mp4audio/wavaudio/webmaudio/ogg
File size limit:
- 25 MB
Behavior:
- stores the audio file in
server/uploads/voice - creates a pending
voice_log - enqueues a voice-processing job
- worker transcribes audio
- worker creates a normal
log_entry - worker queues normal log enrichment
Response:
{
"success": true,
"id": "voice-log-uuid",
"status": "queued",
"audioUrl": "/uploads/voice/1711111111111-note.webm"
}Lists voice logs for the authenticated user.
Auth: required
Response:
{
"success": true,
"data": [
{
"id": "voice-log-uuid",
"status": "completed",
"transcript": "Had coffee and skipped breakfast",
"createdLogEntryId": "log-uuid"
}
]
}Returns one voice log.
Auth: required
Path params:
id: voice log id
Response:
{
"success": true,
"data": {
"id": "voice-log-uuid",
"audioUrl": "/uploads/voice/1711111111111-note.webm",
"status": "completed",
"transcript": "Transcript text",
"processingError": null
}
}Returns the current nutrition goal record for the authenticated user.
Auth: required
Response:
{
"success": true,
"data": {
"userId": "user-uuid",
"dailyCalories": 2200,
"dailyProtein": 140,
"dailyCarbs": 220,
"dailyFat": 70,
"goalType": "maintain",
"activityLevel": "moderate"
}
}Creates or updates nutrition goals.
Auth: required
Request body:
{
"dailyCalories": 2100,
"dailyProtein": 150,
"dailyCarbs": 200,
"dailyFat": 65,
"goalType": "lose",
"activityLevel": "active"
}Validation:
dailyCalories: optional positive integer ornulldailyProtein: optional non-negative number ornulldailyCarbs: optional non-negative number ornulldailyFat: optional non-negative number ornullgoalType: optionallose | maintain | gain | nullactivityLevel: optionalsedentary | light | moderate | active | very_active | null
Response:
{
"success": true,
"data": {
"dailyCalories": 2100,
"goalType": "lose",
"activityLevel": "active"
}
}Returns nutrition summaries for the requested period.
Auth: required
Query params:
period:dayorweek, defaultday
Response:
{
"success": true,
"data": {
"period": "week",
"from": "2026-03-16",
"to": "2026-03-22",
"totals": {
"totalCalories": 13200,
"totalProtein": 910,
"totalCarbs": 1400,
"totalFat": 420,
"breakfastCalories": 2500,
"lunchCalories": 3500,
"dinnerCalories": 5200,
"snackCalories": 2000
},
"averagePerDay": {
"calories": 1885.7,
"protein": 130,
"carbs": 200,
"fat": 60
},
"goalTargets": {
"calories": 2100,
"protein": 150,
"carbs": 200,
"fat": 65
},
"summaries": []
}
}Returns nutrition progress points for charts.
Auth: required
Query params:
days: integer1..365, default30
Response:
{
"success": true,
"data": {
"from": "2026-02-22",
"to": "2026-03-22",
"days": 30,
"points": [
{
"date": "2026-03-22",
"calories": 1900,
"protein": 140,
"carbs": 180,
"fat": 70,
"goalCalories": 2100,
"goalProtein": 150,
"goalCarbs": 200,
"goalFat": 65
}
]
}
}Returns suggested calorie and macro targets based on recent data.
Auth: required
Query params:
days: integer7..90, default14
Response:
{
"success": true,
"data": {
"from": "2026-03-09",
"to": "2026-03-22",
"recommendation": {
"suggestedCalories": 2050,
"suggestedProtein": 145,
"suggestedCarbs": 210,
"suggestedFat": 62,
"reasoning": [
"Adjusted calories slightly below recent intake to support a weight-loss goal.",
"Used the latest recorded weight (78.4 kg) to anchor protein guidance."
]
}
}
}Returns a one-day dashboard combining nutrition, hydration, exercise, and weight summary data.
Auth: required
Response:
{
"success": true,
"data": {
"period": "day",
"from": "2026-03-22",
"to": "2026-03-22",
"goals": {
"calories": 2100,
"protein": 150,
"carbs": 200,
"fat": 65
},
"totals": {
"calories": 1900,
"protein": 145,
"carbs": 185,
"fat": 68,
"waterMl": 2400,
"exerciseMinutes": 35,
"caloriesBurned": 300
},
"latestWeightKg": 78.4,
"days": []
}
}Returns a seven-day dashboard rollup.
Auth: required
Response shape is the same as /api/dashboard/day, but with period: "week" and 7 date entries in days.
Returns adherence counts derived from nutrition and health summary tables.
Auth: required
Query params:
days: integer1..365, default7
Current adherence rules:
- calorie goal hit: day calories
<= dailyCalories - protein goal hit: day protein
>= dailyProtein - hydration hit: water
>= 2000ml - exercise hit: exercise
>= 20 minutes - logging hit: at least one text log or completed food log that day
Response:
{
"success": true,
"data": {
"days": 7,
"calorieGoalDaysHit": 5,
"proteinGoalDaysHit": 4,
"hydrationDaysHit": 6,
"exerciseDaysHit": 3,
"loggingDaysHit": 7,
"summaryText": "4 of last 7 days hit protein goal, 6 met hydration, and 3 included exercise."
}
}Returns high-level weekly patterns derived from recent summary tables.
Auth: required
Response:
{
"success": true,
"data": {
"from": "2026-03-16",
"to": "2026-03-22",
"insights": [
{
"type": "protein",
"title": "Protein is trending low",
"message": "Average protein intake was 108g, below your 150g target.",
"severity": "warning"
},
{
"type": "calories",
"title": "Dinner is carrying a lot of calories",
"message": "4 of the last 7 days had 45% or more of calories at dinner.",
"severity": "info"
}
]
}
}Possible insight types:
proteinhydrationcaloriesexerciseloggingweight
Creates a water log entry.
Auth: required
Request body:
{
"amountMl": 500,
"loggedAt": "2026-03-22T09:00:00.000Z"
}Validation:
amountMl: positive integer, max10000loggedAt: optional date
Response:
{
"success": true,
"data": {
"id": "water-log-uuid",
"amountMl": 500,
"loggedAt": "2026-03-22T09:00:00.000Z"
}
}Lists water logs, newest first.
Auth: required
Deletes a water log and refreshes daily health summary.
Auth: required
Path params:
id: water log id
Response:
{
"success": true,
"message": "Water log deleted"
}Creates an exercise log.
Auth: required
Request body:
{
"activityType": "Running",
"durationMinutes": 30,
"estimatedCaloriesBurned": 280,
"notes": "Zone 2 cardio",
"loggedAt": "2026-03-22T18:00:00.000Z"
}Validation:
activityType: required string,1..120charsdurationMinutes: positive integer, max1440estimatedCaloriesBurned: optional non-negative integer ornullnotes: optional, max500loggedAt: optional date
Response:
{
"success": true,
"data": {
"id": "exercise-log-uuid",
"activityType": "Running",
"durationMinutes": 30
}
}Lists exercise logs, newest first.
Auth: required
Deletes an exercise log and refreshes daily health summary.
Auth: required
Path params:
id: exercise log id
Response:
{
"success": true,
"message": "Exercise log deleted"
}Creates a weight log.
Auth: required
Request body:
{
"weightKg": 78.4,
"notes": "Morning weight",
"loggedAt": "2026-03-22T07:30:00.000Z"
}Validation:
weightKg: positive number, max500notes: optional, max500loggedAt: optional date
Response:
{
"success": true,
"data": {
"id": "weight-log-uuid",
"weightKg": 78.4,
"loggedAt": "2026-03-22T07:30:00.000Z"
}
}Lists weight logs, newest first.
Auth: required
Deletes a weight log and refreshes daily health summary.
Auth: required
Path params:
id: weight log id
Response:
{
"success": true,
"message": "Weight log deleted"
}These are served directly by Express:
/uploads/food/:filename/uploads/voice/:filename
These are usually returned inside food and voice log records as imageUrl or audioUrl.
- Use
multipart/form-datafor/api/foodand/api/voice-logs - Use
application/jsoneverywhere else - Poll
GET /api/food/:idafter photo upload if you want to wait for AI processing to finish - Poll
GET /api/voice-logs/:idafter audio upload if you want to wait for transcription to finish - For manual food entry, use
/api/food/manualdirectly and no polling is needed - Dashboard and insights endpoints are read-only and are good candidates for homepage widgets
- onboarding / auth:
Better Authroutes under/api/auth/* - home dashboard:
/api/dashboard/day,/api/dashboard/week,/api/insights/weekly - chat screen:
/api/chat - meal capture:
- photo upload:
/api/food - manual meal:
/api/food/manual - meal details:
/api/food/:id - meal history:
/api/food/:id/history
- photo upload:
- goals screen:
/api/goals/api/goals/nutrition/api/goals/progress/api/goals/adherence/api/goals/recommendations
- hydration:
/api/water - exercise:
/api/exercise - weight:
/api/weight - voice notes:
/api/voice-logs