Log a meal or workout from photos
Uploads 1–5 photos, classifies what they show — food, workout evidence (machine console, app/watch summary, whiteboard), a scale readout, or a tape-measure/measuring-app reading — and logs the matching entry (meal, workout, weigh-in, or body measurement). Returns the classification in kind.
/api/log/analyze__Secure-better-auth.session_tokenAPI key · cookierequiredBetter Auth session. Signing in (Google or email magic link) sets an HttpOnly session cookie that scopes every request to that account. The web app and CLI use this; agents use the OAuth 2.1 flow of the MCP server.
AuthorizationBearer token · headerrequiredPersonal API key sent as Authorization: Bearer skcal_…. Create and scope keys in the app under your profile → API keys. Keys default to full access; a scoped key is limited to its granted resource:action permissions and cannot manage other keys.
datestring<date>Day to log against (YYYY-MM-DD). Defaults to today.
tzintegerTimezone offset in minutes (as from getTimezoneOffset), used to anchor a dated workout.
multipart/form-dataThe photos (multipart field photos), plus an optional note caption for context.
photosstring<binary>[]required1–5 photos of one meal or one workout (JPEG, PNG, WebP, or GIF; max 8MB each).
notestringOptional caption; trusted over the pixels when they disagree.
The photos were classified and, unless kind is other, logged.
kindstringrequiredWhat the photos were classified as.
foodworkoutweightmeasurementotherokbooleanTrue when something was logged.
mealIdstring<uuid>Created meal id (food only).
itemsAnalyzedItem[]Estimated food items (food only).
Show propertiesHide properties
AnalyzedItemnamestringrequiredEstimated food item name.
kcalintegerrequiredEstimated calories for the portion.
proteinGnumberrequiredEstimated protein in grams.
totalKcalintegerSummed calories (food only).
totalProteinGintegerSummed protein grams (food only).
notestringModel assumptions note (food only).
photoKeysstring[]Stored photo keys (food only).
workoutWorkoutA logged workout, normalized to the standard fields of Apple Health, Garmin, and Strava; metric fields are null when the user didn't state them.
Show propertiesHide properties
idstring<uuid>requiredWorkout identifier.
sourcestringrequiredWhere the workout came from; manual for described/photographed logs.
manualapple_healthgarminstravaactivityTypestringrequiredNormalized activity type (run, ride, swim, walk, hike, strength_training, yoga, rowing, elliptical, crossfit, other).
summarystringrequiredShort AI-generated title.
descriptionstringrequiredThe user's original words (or the summary for photo-only logs).
startedAtintegerrequiredStart time as Unix milliseconds.
durationSinteger | nullrequiredElapsed duration in seconds.
movingDurationSinteger | nullrequiredMoving time in seconds, when distinct from elapsed.
distanceMnumber | nullrequiredDistance in meters.
elevationGainMnumber | nullrequiredElevation gain in meters.
energyKcalnumber | nullrequiredActive energy in kilocalories.
avgHrinteger | nullrequiredAverage heart rate in bpm.
maxHrinteger | nullrequiredMax heart rate in bpm.
avgPowerWnumber | nullrequiredAverage power in watts.
avgCadencenumber | nullrequiredAverage cadence (spm or rpm as reported).
exercisesWorkoutExercise[]requiredStrength breakdown; empty for pure cardio.
Show propertiesHide properties
WorkoutExerciseexercisestringrequiredExercise name as the user gave it.
setsinteger | nullrequiredNumber of sets, when stated.
repsinteger | nullrequiredReps per set, when stated.
weightLbnumber | nullrequiredWorking weight in pounds, when stated.
durationSinteger | nullrequiredDuration in seconds for timed pieces, when stated.
distanceMnumber | nullrequiredDistance in meters for distance pieces, when stated.
notesstring | nullrequiredAnything else the user said about the exercise.
createdAtintegerrequiredRow creation time as Unix milliseconds.
poundsnumberLogged body weight in pounds (weight only).
weightKgnumberLogged body weight in kilograms (weight only).
bodyFatPctnumber | nullBody-fat percentage when the scale showed one (weight only).
sitestringBody site the measurement was logged against (measurement only).
inchesnumberMeasured value in inches (measurement only).
needSitebooleanTrue when a measurement was read but the body site could not be identified — nothing was logged; resubmit with a caption naming the site.
The request body or parameters failed validation.
errorstringrequiredHuman-readable failure reason.
detailstringOptional extra context, present on upstream failures.
The upstream model call failed.
errorstringrequiredHuman-readable failure reason.
detailstringOptional extra context, present on upstream failures.
A required secret (model key or ingest token) is not configured.
errorstringrequiredHuman-readable failure reason.
detailstringOptional extra context, present on upstream failures.