# Gym API > Personal, mobile-first gym tracker for one person (Abdellatif). You, the coding agent, write his training plan through this JSON API. He opens the app, starts the next workout and logs sets, which are prefilled from his history. No auth, CORS open, JSON in and out. - Base URL: https://gym.abdellatif.io/api - Full reference with more recipes: https://gym.abdellatif.io/api/docs - OpenAPI 3.1: https://gym.abdellatif.io/api/openapi.json - Index: https://gym.abdellatif.io/api ## Data model - **Workout**: one session. `id`, `title`, `date` (YYYY-MM-DD), `status`, `program_id`, `position`, `warmup`, `notes` (plan notes he sees), `log_notes` (his notes after training), `source` (`api` for you). - Status lifecycle: `planned` (your plans) → `in_progress` (he started it) → `completed` (history); or `skipped`. - **Item**: one exercise in a workout (`items[]`, in order): `exercise_id` (null for a free-form block), `name`, `label` (superset: A1, A2, A3), `prescription` (coach text), `cue`, `status`, `result_text`, `notes` (his skip reasons). - Parsed targets on items: `target_sets`, `target_reps` ([12, 10, 8]; null = max reps), `rep_min`, `rep_max`, `target_seconds`, `rest_seconds`, `rir`. - **Set**: `weight` + `unit` (kg or lb, as entered) + `reps` or `seconds`, `rir`, `is_warmup`, `done`. Nested in `items[].sets[]`. - Plans have no sets: they are created from the suggestion when he starts the workout. - **Exercise**: canonical movement, slug id (`incline-bench-press`), `name`, `aliases`, `muscle_group`, `equipment`, `tracking` (weight_reps | reps | time | text), `default_unit`, `weight_step`, `notes`. - Exercise detail = history (every completed session), records (PRs) and the next suggestion. - **Program**: optional grouping of workouts (a training block): `id`, `name`, `description`, `status` (active | archived). - **Next workout** = the in-progress one, else the earliest planned by `date` (unscheduled last), then `position`. Overdue plans stay next. - **Suggestion** = prefill per set: last time's weights, reps from the prescription, +1 weight step when he hit the top of last time's reps, same weight after 4+ weeks off. - Weights are never converted; fields ending in `_kg` are converted for analytics. - Timestamps (`*_at`) are UTC; pass `?today=YYYY-MM-DD` (his local day) to endpoints that depend on the date. - Every write returns the updated resource; errors are `{"error": {"code", "message"}}` with a 4xx status. - Ids: send your own (`w_2026-09-28_chest`, `^[A-Za-z0-9_-]{1,64}$`) so retries are safe. ## How he trains (for judgement calls) - A 3-day rotation without fixed weekdays, about two sessions a week (median gap 3 days). "Week N" in a title means one rotation. - Recent split: Chest + Biceps + Core / Back + Triceps + Core / Shoulders + Arms + Legs. Each day is three tri-sets (A1-A3, B1-B3, C1-C3), core in the third slot, 9-10 items. - Reps wave week to week within a 4-week block, e.g. 12-15 → 15-12-10 → 12-10-8 → 10-10-8, rest 30-60s between exercises. - Cables and stack machines are often in lb, dumbbells and barbells in kg. - Before planning, read item `notes` (skip reasons such as "Machine not available", injuries), workout `log_notes` and exercise `notes`. ## Recipes Commands are copy-pasteable (bash or zsh). jq is only used to trim or edit output. The examples plan the week of Mon 28 Sep 2026; replace dates and ids. ### 1. See what's next and what's planned Start here. `workout` is what the app offers next, with prefill; `upcoming` is the rest of the queue. A planned workout with a past date is overdue and stays next until it is done, moved, skipped or deleted. ```bash curl -sS "https://gym.abdellatif.io/api/next" ``` Every planned workout, earliest date first: ```bash curl -sS "https://gym.abdellatif.io/api/workouts?status=planned" ``` One line per workout, in queue order (needs jq): ```bash curl -sS "https://gym.abdellatif.io/api/workouts?status=planned,in_progress&order=asc" | jq -r '.workouts[] | "\(.date) \(.status) \(.id) \(.title)"' ``` ### 2. Find the exercise names to use Plans reference exercises by name, and only a name that matches an exercise (its name or an alias, ignoring case, spacing, hyphens and plurals) links to his history. Search before writing a plan (every word of `q` must appear), and prefer exercises he has done (`times_performed`, `last_performed` count sessions where he did it, not ones he skipped). Read `notes` too (e.g. a machine his gym doesn't have). Search names and aliases: ```bash curl -sS "https://gym.abdellatif.io/api/exercises?q=curl" ``` By muscle group, most performed first: ```bash curl -sS "https://gym.abdellatif.io/api/exercises?muscle_group=chest&sort=frequency" ``` The whole library, one line each (id, name, muscle, times done, last done): ```bash curl -sS "https://gym.abdellatif.io/api/exercises?sort=frequency" | jq -r '.exercises[] | [.id, .name, .muscle_group, .times_performed, .last_performed] | @tsv' ``` ### 3. Check an exercise's history and PRs before progressing it `history` is every completed session, newest first, with the prescription at the time and the sets he logged. `records` holds his PRs and `suggestion` is what the app would prefill next. Weights are as entered (kg or lb per set). ```bash curl -sS "https://gym.abdellatif.io/api/exercises/incline-bench-press" ``` The last four sessions, compact: ```bash curl -sS "https://gym.abdellatif.io/api/exercises/incline-bench-press" | jq '{records, next: .suggestion.reason, history: [.history[:4][] | {date, prescription, sets: [.sets[] | "\(.weight) \(.unit) x \(.reps)"]}]}' ``` What the app would prefill for a prescription you are considering (`max` marks a max-reps set): ```bash curl -sS "https://gym.abdellatif.io/api/exercises/incline-bench-press/suggestion?target_reps=12,10,8&today=$(date +%F)" ``` ### 4. Read recent sessions and stats Before planning, read what he actually did: completed workouts, his `log_notes`, and item `notes` (skip reasons such as `Skipped: Machine not available.`). `prefill=0` leaves out the suggestion fields. The last six completed workouts (summaries): ```bash curl -sS "https://gym.abdellatif.io/api/workouts?status=completed&limit=6" ``` One workout in full: items, prescriptions, sets and notes: ```bash curl -sS "https://gym.abdellatif.io/api/workouts/w_0f676309a14e?prefill=0" ``` A month at a glance, one line per workout: ```bash curl -sS "https://gym.abdellatif.io/api/workouts?status=completed&from=2026-08-01&to=2026-08-31&order=asc" | jq -r '.workouts[] | "\(.date) \(.title) \(.done_set_count) sets \(.volume_kg) kg"' ``` Totals, weekly volume, streak, muscle recency and recent PRs, for your local day: ```bash curl -sS "https://gym.abdellatif.io/api/stats?today=$(date +%F)" ``` ### 5. Create a program A program groups the workouts of a block so you can list or clear them together. It is optional. Choose the id yourself so the next calls can use it straight away. ```bash curl -sS -X POST "https://gym.abdellatif.io/api/programs" \ -H 'Content-Type: application/json' \ --data-binary @- <<'JSON' { "id": "p_oct_2026", "name": "Hypertrophy block Oct 2026", "description": "4 weeks, 3-day rotation: Chest + Biceps + Core / Back + Triceps + Core / Shoulders + Arms + Legs. Reps wave 12-10-8 → 15-12-10 → 12-10-8 → 10-10-8." } JSON ``` Programs with workout counts, and one program with its workouts: ```bash curl -sS "https://gym.abdellatif.io/api/programs" curl -sS "https://gym.abdellatif.io/api/programs/p_oct_2026" ``` ### 6. Plan next week: one workout per training day One `POST /api/workouts` per training day, with the items nested in order. Reference exercises by name, put the coach-style text in `prescription` (targets are parsed from it) and label supersets A1/A2/A3. Don't send sets or weights: when he starts, the app prefills every set from his history and the prescription. With your own ids a retried POST returns 409 instead of a duplicate (`PUT /api/workouts/{id}` with the same body is an idempotent alternative: items without ids are matched to the current ones, so their ids stay the same). Check `created_exercises` in each response: it lists names that matched no exercise. Monday: ```bash curl -sS -X POST "https://gym.abdellatif.io/api/workouts" \ -H 'Content-Type: application/json' \ --data-binary @- <<'JSON' { "id": "w_2026-09-28_chest", "program_id": "p_oct_2026", "title": "Chest + Biceps + Core", "date": "2026-09-28", "notes": "Week 1/4. The last set should always be challenging.", "items": [ {"label": "A1", "exercise": "Incline Bench Press", "prescription": "3 x 12-10-8 reps, rest 30-60s\n*Go down in 3 sec*"}, {"label": "A2", "exercise": "Alternating Dumbbell Curl", "prescription": "3 x 14-12-10 reps, rest 30-60s"}, {"label": "A3", "exercise": "Hollow Body Hold", "prescription": "3x 30-40s, rest as needed"}, {"label": "B1", "exercise": "Dumbbell Fly", "prescription": "3 x 10-12 reps, rest 30-60s\n*Slow and Controlled*"}, {"label": "B2", "exercise": "Barbell Curl", "prescription": "3 x 8-10 reps, rest 30-60s"}, {"label": "B3", "exercise": "Med Ball Russian Twist", "prescription": "3x 30-40s, rest as needed"}, {"label": "C1", "exercise": "Hand Release Push-Up", "prescription": "3 x Max reps (-2), rest 30-60s\n*Leave 2 reps in the tank*"}, {"label": "C2", "exercise": "Single Arm Dumbbell Zottman Curl", "prescription": "3 x 8-10 reps/side, rest 30-60s"}, {"label": "C3", "exercise": "Hanging Oblique Raises", "prescription": "3x 30-40s, rest as needed"} ] } JSON ``` Wednesday: ```bash curl -sS -X POST "https://gym.abdellatif.io/api/workouts" \ -H 'Content-Type: application/json' \ --data-binary @- <<'JSON' { "id": "w_2026-09-30_back", "program_id": "p_oct_2026", "title": "Back + Triceps + Core", "date": "2026-09-30", "notes": "Week 1/4.", "items": [ {"label": "A", "exercise": "Strict Pull-Up", "prescription": "3 Sets, 5-7 reps (1-sec pause at the top)\nRest as needed"}, {"label": "B1", "exercise": "V-Bar Cable Lat Pulldown Machine", "prescription": "3 x 12-10-8 reps, rest 30-60s\n*Go up in 3 sec, controlled reps*"}, {"label": "B2", "exercise": "Dumbbell Incline Tricep Extension", "prescription": "3 x 12-10-8 reps, rest 30-60s"}, {"label": "B3", "exercise": "Hollow Body Hold", "prescription": "3x 30-40s, rest as needed"}, {"label": "C1", "exercise": "Bent Over Barbell Row", "prescription": "3 x 12-10-8 reps, rest 30-60s\n*Squeeze at the top and hold for 1 sec*"}, {"label": "C2", "exercise": "Single Arm Tricep Pushdown", "prescription": "3 x 10-12 reps/side, rest 30-60s\n*Locked elbow*"}, {"label": "C3", "exercise": "Alternating V-Ups", "prescription": "3x 16-20 Alt Reps, rest as needed"} ] } JSON ``` Friday (the `D` block has no exercise: it is a free-form circuit logged as text): ```bash curl -sS -X POST "https://gym.abdellatif.io/api/workouts" \ -H 'Content-Type: application/json' \ --data-binary @- <<'JSON' { "id": "w_2026-10-02_legs", "program_id": "p_oct_2026", "title": "Shoulders + Arms + Legs", "date": "2026-10-02", "notes": "Week 1/4.", "items": [ {"label": "A1", "exercise": "Seated Alternating Arnold Dumbbell Press", "prescription": "3 x 12-10-8 reps, rest 30-60s\n*Go down in 3 sec*"}, {"label": "A2", "exercise": "One and a Quarter Goblet Squat", "prescription": "3 x 12-10-8 reps, rest 30-60s\n*Go down in 3 sec*"}, {"label": "A3", "exercise": "Dumbbell Curl", "prescription": "3 x 12-10-8 reps, rest 30-60s"}, {"label": "B1", "exercise": "Dumbbell Tricep Extension", "prescription": "3 x 12-10-8 reps, rest 30-60s"}, {"label": "B2", "exercise": "Dumbbell Walking Lunge", "prescription": "3 x 16-14-12 alternating reps, rest as needed"}, {"label": "B3", "exercise": "Chainsaw Row", "prescription": "3 x 8-10 reps/side, rest 30-60s"}, {"label": "C1", "exercise": "Dumbbell Hip Thrust", "prescription": "3 x 10-12 reps @2 sec pause at the top"}, {"label": "C2", "exercise": "Leg Extension Machine", "prescription": "3 x 12-15 reps, rest 30-60s"}, {"label": "D", "name": "Core circuit", "prescription": "3 Sets:\n12 DB Side Plank Rotations/side\n20 Hollow rocks\n20 Alternating V-ups\n-Rest 60-90 Sec-"} ] } JSON ``` ### 7. Change a planned workout From the smallest change to a full rewrite. Item ids come from the workout (`GET …?prefill=0`). Commands in this recipe reuse `$ITEM` from the first one. Find an item by its label: ```bash ITEM=$(curl -sS "https://gym.abdellatif.io/api/workouts/w_2026-09-28_chest?prefill=0" | jq -r '.items[] | select(.label == "B1") | .id') ``` Change its prescription (targets and cue are parsed again): ```bash curl -sS -X PATCH "https://gym.abdellatif.io/api/items/$ITEM" \ -H 'Content-Type: application/json' \ -d '{"prescription": "3 x 12-10-8 reps, rest 30-60s\n*Slow and controlled*"}' ``` Swap the exercise, keeping label and prescription (the item takes the new exercise's name): ```bash curl -sS -X PATCH "https://gym.abdellatif.io/api/items/$ITEM" \ -H 'Content-Type: application/json' \ -d '{"exercise": "Chest Fly Machine"}' ``` Add an exercise at the end (send `position` to put it elsewhere, then reorder): ```bash curl -sS -X POST "https://gym.abdellatif.io/api/workouts/w_2026-09-28_chest/items" \ -H 'Content-Type: application/json' \ --data-binary @- <<'JSON' { "id": "i_2026-09-28_chest_d", "label": "D", "exercise": "Dumbbell Hammer Curl", "prescription": "2 x 15-20 reps, rest 30s\n*Squeeze at the top*" } JSON ``` Remove an item: ```bash curl -sS -X DELETE "https://gym.abdellatif.io/api/items/i_2026-09-28_chest_d" ``` Reorder: `item_ids` must list every item. This sorts them by label: ```bash curl -sS "https://gym.abdellatif.io/api/workouts/w_2026-09-28_chest?prefill=0" | jq '{item_ids: (.items | sort_by(.label) | map(.id))}' \ | curl -sS -X POST "https://gym.abdellatif.io/api/workouts/w_2026-09-28_chest/reorder" -H 'Content-Type: application/json' --data-binary @- ``` GET, edit, PUT back. The GET response is valid PUT input; a changed `prescription` is parsed again (the old target fields next to it are ignored), and a changed `exercise` name swaps the exercise: ```bash curl -sS "https://gym.abdellatif.io/api/workouts/w_2026-09-28_chest?prefill=0" \ | jq '.notes = "Week 1/4. Heavier than last cycle." | (.items[] | select(.label == "A1")) |= (.prescription = "4 x 10-8-8-6 reps, rest 60-90s\n*Go down in 3 sec*")' \ | curl -sS -X PUT "https://gym.abdellatif.io/api/workouts/w_2026-09-28_chest" -H 'Content-Type: application/json' --data-binary @- ``` Rewrite a plan completely: PUT the new version. Items without ids take the id of a current item with the same label and exercise; the rest replace the current items. On a plan nothing is lost; on a started workout a PUT that would delete logged sets is refused (409 `would_lose_logged_sets`): ```bash curl -sS -X PUT "https://gym.abdellatif.io/api/workouts/w_2026-10-02_legs" \ -H 'Content-Type: application/json' \ --data-binary @- <<'JSON' { "title": "Lower Body", "notes": "Week 1/4. No overhead pressing this week (shoulder).", "items": [ {"label": "A1", "exercise": "One and a Quarter Goblet Squat", "prescription": "3 x 12-10-8 reps, rest 30-60s\n*Go down in 3 sec*"}, {"label": "A2", "exercise": "Prone Hamstring Curl Machine", "prescription": "3 x 12-10-8 reps, rest 30-60s"}, {"label": "B1", "exercise": "Dumbbell Walking Lunge", "prescription": "3 x 16-14-12 alternating reps, rest 30-60s"}, {"label": "B2", "exercise": "Leg Extension Machine", "prescription": "3 x 12-15 reps, rest 30-60s"}, {"label": "C1", "exercise": "Dumbbell Hip Thrust", "prescription": "3 x 10-12 reps @2 sec pause at the top"}, {"label": "C2", "exercise": "Leg Press Machine Calf Raises", "prescription": "3 x 15-20 reps, rest 30-60s\n*Pause 1 sec at the bottom*"} ] } JSON ``` ### 8. Reschedule, reorder, skip, restore or delete planned workouts The queue is ordered by `date`, then `position`. Skipping keeps a workout (restorable); deleting removes it with its items. Don't delete or rewrite completed workouts unless he asks. Move a plan to another day: ```bash curl -sS -X PATCH "https://gym.abdellatif.io/api/workouts/w_2026-09-30_back" \ -H 'Content-Type: application/json' \ -d '{"date": "2026-10-01"}' ``` The overdue plan from 7 Sep (Week 1/4) is still next. Move it to tomorrow… ```bash curl -sS -X PATCH "https://gym.abdellatif.io/api/workouts/w_486229e6a086" \ -H 'Content-Type: application/json' \ -d '{"date": "2026-09-25"}' ``` …or skip it: ```bash curl -sS -X POST "https://gym.abdellatif.io/api/workouts/w_486229e6a086/skip" ``` Restore a skipped workout to the plan: ```bash curl -sS -X PATCH "https://gym.abdellatif.io/api/workouts/w_486229e6a086" \ -H 'Content-Type: application/json' \ -d '{"status": "planned", "date": "2026-09-25"}' ``` Two plans on the same day: the lower `position` goes first: ```bash curl -sS -X PATCH "https://gym.abdellatif.io/api/workouts/w_486229e6a086" \ -H 'Content-Type: application/json' \ -d '{"position": -1}' ``` Repeat a day next week (items and prescriptions, no sets): ```bash curl -sS -X POST "https://gym.abdellatif.io/api/workouts/w_2026-09-28_chest/duplicate" \ -H 'Content-Type: application/json' \ -d '{"id": "w_2026-10-05_chest", "date": "2026-10-05"}' ``` Delete a plan: ```bash curl -sS -X DELETE "https://gym.abdellatif.io/api/workouts/w_2026-10-05_chest" ``` ## Rules and gotchas - **Exercise names**: use existing names from `GET /api/exercises` (matched case-insensitively, ignoring spaces, hyphens, apostrophes and plurals; aliases count). A name that matches nothing creates a new exercise with no history, so the app cannot prefill it: check `created_exercises` in the write response. Merge accidental duplicates with `POST /api/exercises/{id}/merge`. - **Supersets**: label items A1, A2, A3 (then B1…, C1…); a bare letter (D) is a single block. Keep each group together; items show in array order. - **Prescriptions**: put the coach-style text in `prescription`, e.g. `"3 x 12-10-8 reps, rest 30-60s\n*Go down in 3 sec*"`. Sets, reps, range, rest, RIR (`Max reps (-2)`, `@2 RIR`) and cue are parsed from it. Only send target fields to override the parser. Check with `POST /api/parse`. - **Free-form blocks** (AMRAP, circuits, cardio): an item with `name` and `prescription` but no `exercise` (a `name` that is exactly an exercise's name links to it). - **No weights in plans**: don't send `sets`; the app prefills from history. Exception: to prescribe a specific load on purpose (a deload), send undone `sets` with the weights and say why in `cue`; start keeps them. - **Dates**: `YYYY-MM-DD`, one workout per training day. The next workout is the earliest planned one, so skip, move or delete stale overdue plans (ask him if unsure). - **Status lifecycle**: you create `planned` workouts. Use the actions to change state: `POST …/skip` for plans (undo with `PATCH {"status": "planned"}`; completed or in-progress workouts are refused); start and complete belong to the app. - **Completed workouts are his history**: don't modify or delete them unless he asks. - **PUT merge rules**: omitted fields are kept; `items` is the complete list (listed ids updated; items without `id` update the current item with the same label and exercise, else are created; missing ones deleted); `sets` per item likewise; omit `items` or `sets` to keep them. `PATCH` never takes `items`. A PUT that would delete or undo logged sets is a 409 (`would_lose_logged_sets`): on a started or completed workout, change items and sets with PATCH. - **Editing a GET response for PUT**: works as is; a changed `prescription` is parsed again and a changed `exercise` name swaps the exercise. Never copy set ids from `last.sets` (they belong to his history: 409). - **Retries**: choose ids yourself; a repeated POST returns 409 (nothing duplicated), a repeated PUT is harmless (item ids stay the same). - **Public API**: no auth, anyone can read it. Never store secrets in notes. ## More - https://gym.abdellatif.io/api/docs: every endpoint, the merge and suggestion rules, limits, errors, types and more recipes (export/import, recording a past session, parser preview). - https://gym.abdellatif.io/api/openapi.json: machine-readable spec.