Skip to content
skcal
Esc
↑↓navigate↵open⌘Jpreview

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.

POST/api/log/analyze
Authorization
__Secure-better-auth.session_tokenAPI key · cookierequired

Better 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.

or
AuthorizationBearer token · headerrequired

Personal 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.

Query parameters
datestring<date>

Day to log against (YYYY-MM-DD). Defaults to today.

tzinteger

Timezone offset in minutes (as from getTimezoneOffset), used to anchor a dated workout.

Request body
requiredmultipart/form-data

The photos (multipart field photos), plus an optional note caption for context.

photosstring<binary>[]required

1–5 photos of one meal or one workout (JPEG, PNG, WebP, or GIF; max 8MB each).

notestring

Optional caption; trusted over the pixels when they disagree.

max length 2000
Responses
200

The photos were classified and, unless kind is other, logged.

kindstringrequired

What the photos were classified as.

Allowed:foodworkoutweightmeasurementother
okboolean

True when something was logged.

mealIdstring<uuid>

Created meal id (food only).

itemsAnalyzedItem[]

Estimated food items (food only).

Show properties
Array of AnalyzedItem
namestringrequired

Estimated food item name.

kcalintegerrequired

Estimated calories for the portion.

proteinGnumberrequired

Estimated protein in grams.

totalKcalinteger

Summed calories (food only).

totalProteinGinteger

Summed protein grams (food only).

notestring

Model assumptions note (food only).

photoKeysstring[]

Stored photo keys (food only).

workoutWorkout

A 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 properties
idstring<uuid>required

Workout identifier.

sourcestringrequired

Where the workout came from; manual for described/photographed logs.

Allowed:manualapple_healthgarminstrava
activityTypestringrequired

Normalized activity type (run, ride, swim, walk, hike, strength_training, yoga, rowing, elliptical, crossfit, other).

summarystringrequired

Short AI-generated title.

descriptionstringrequired

The user's original words (or the summary for photo-only logs).

startedAtintegerrequired

Start time as Unix milliseconds.

durationSinteger | nullrequired

Elapsed duration in seconds.

movingDurationSinteger | nullrequired

Moving time in seconds, when distinct from elapsed.

distanceMnumber | nullrequired

Distance in meters.

elevationGainMnumber | nullrequired

Elevation gain in meters.

energyKcalnumber | nullrequired

Active energy in kilocalories.

avgHrinteger | nullrequired

Average heart rate in bpm.

maxHrinteger | nullrequired

Max heart rate in bpm.

avgPowerWnumber | nullrequired

Average power in watts.

avgCadencenumber | nullrequired

Average cadence (spm or rpm as reported).

exercisesWorkoutExercise[]required

Strength breakdown; empty for pure cardio.

Show properties
Array of WorkoutExercise
exercisestringrequired

Exercise name as the user gave it.

setsinteger | nullrequired

Number of sets, when stated.

repsinteger | nullrequired

Reps per set, when stated.

weightLbnumber | nullrequired

Working weight in pounds, when stated.

durationSinteger | nullrequired

Duration in seconds for timed pieces, when stated.

distanceMnumber | nullrequired

Distance in meters for distance pieces, when stated.

notesstring | nullrequired

Anything else the user said about the exercise.

createdAtintegerrequired

Row creation time as Unix milliseconds.

poundsnumber

Logged body weight in pounds (weight only).

weightKgnumber

Logged body weight in kilograms (weight only).

bodyFatPctnumber | null

Body-fat percentage when the scale showed one (weight only).

sitestring

Body site the measurement was logged against (measurement only).

inchesnumber

Measured value in inches (measurement only).

needSiteboolean

True when a measurement was read but the body site could not be identified — nothing was logged; resubmit with a caption naming the site.

400

The request body or parameters failed validation.

errorstringrequired

Human-readable failure reason.

detailstring

Optional extra context, present on upstream failures.

502

The upstream model call failed.

errorstringrequired

Human-readable failure reason.

detailstring

Optional extra context, present on upstream failures.

503

A required secret (model key or ingest token) is not configured.

errorstringrequired

Human-readable failure reason.

detailstring

Optional extra context, present on upstream failures.

Try it
Server
Authorization
Parameters
Bodymultipart/form-data
Request
curl -X POST 'https://app.skcal.fit/api/log/analyze' \
  -H 'Cookie: __Secure-better-auth.session_token=YOUR_API_KEY' \
  -H 'Accept: application/json' \
  --form-string 'photos=@treadmill.jpg' \
  --form-string 'note=today'\''s treadmill session'
Response
{
  "kind": "workout",
  "ok": true,
  "mealId": "3f8b1c2a-5d6e-4f70-8a1b-2c3d4e5f6071",
  "items": [
    {
      "name": "grilled chicken breast",
      "kcal": 284,
      "proteinG": 53.4
    }
  ],
  "totalKcal": 374,
  "totalProteinG": 54,
  "note": "assumed a single chicken breast",
  "photoKeys": [
    "nick@mintlify.com/2026-06-29/3f8b1c2a"
  ],
  "workout": {
    "id": "9c2d1e4f-7a8b-4c5d-9e0f-1a2b3c4d5e6f",
    "source": "manual",
    "activityType": "run",
    "summary": "5k run + pullups",
    "description": "ran 5k easy, then 3x10 pullups",
    "startedAt": 1783100000000,
    "durationS": 1680,
    "movingDurationS": null,
    "distanceM": 5000,
    "elevationGainM": null,
    "energyKcal": 320,
    "avgHr": 148,
    "maxHr": null,
    "avgPowerW": null,
    "avgCadence": null,
    "exercises": [
      {
        "exercise": "pullups",
        "sets": 3,
        "reps": 10,
        "weightLb": null,
        "durationS": null,
        "distanceM": null,
        "notes": null
      }
    ],
    "createdAt": 1783100001000
  },
  "pounds": 158.2,
  "weightKg": 71.8,
  "bodyFatPct": 17.2,
  "site": "waist",
  "inches": 31.8,
  "needSite": false
}

Was this page helpful?