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

Log a workout from a description

Parses a freeform description of a workout into a normalized row (activity type, duration, distance, heart rate, strength exercises) and logs it.

POST/api/workouts/describe
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.

Request body
requiredapplication/json

The workout description and optional local day.

textstringrequired

The workout in the user's own words.

max length 4000
datestring<date>

Local day to log against (YYYY-MM-DD); defaults to now.

tzinteger

Timezone offset in minutes, as from getTimezoneOffset.

Responses
200

The workout was parsed and logged.

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.

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
Bodyapplication/json
Request
curl -X POST 'https://app.skcal.fit/api/workouts/describe' \
  -H 'Cookie: __Secure-better-auth.session_token=YOUR_API_KEY' \
  -H 'Accept: application/json' \
  -H 'Content-Type: application/json' \
  -d '{
  "text": "ran 5k easy in 28 minutes, then 3x10 pullups",
  "date": "2026-07-02",
  "tz": 420
}'
Response
{
  "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
}

Was this page helpful?