Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 40 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,33 @@ When you pull workout details, Tonal gives you remarkably rich per-set data:
| `inconsistencyScore` | Rep-to-rep consistency |
| `movementSide` | "Both", "Left", or "Right" |

Both the CLI `detail` command and MCP `get_workout_detail` project ROM,
consistency, spotter, eccentric, chains, burnout, drop-set, and duration fields.
The reserved Rest movement (`00000000-0000-0000-0000-000000000005`) is
excluded. Difficulty/consistency sentinels (`-1`) are returned as `null`.
Difficulty, consistency, and suggested-weight placeholder zeros on explicitly
unperformed sets are also `null`, while valid zero scores on performed sets
remain `0`. Actual/count fields stay numeric, and configured mode flags remain
available, when a prescribed set was not performed.

For catalog movements with `onMachine: false` and `countReps: false`, detail
responses use `measurement_type: "duration"`, expose `duration_sec` and
`prescribed_duration_sec` plus Tonal's separate `duration_based_rep_goal`, and
return resistance-only measurements such as reps, pounds, volume, 1RM, power,
ROM, and difficulty as `null`. Per-movement volume aggregates remain numeric
and use `0` for off-machine work so mixed-workout totals remain safely
summable. Workout `total_volume_lbs` is Tonal's digital-resistance volume; it
does not quantify off-machine bodyweight work.
`measurement_type` describes repetition-versus-duration counting; a timed
on-machine movement can still carry measured resistance fields.

Raw detail frequently reports `movementSide: "Both"` even for unilateral
movements. Use MCP `get_performance_summary` when you need the formatted
`left` / `right` set objects; `get_workout_detail` does not make a second API
request to join those objects into the raw response. CLI users can get the
same formatted source with `performance <activity_id>` (`left_side` /
`right_side`).

## Workout JSON format

Workouts are defined as blocks of exercises. Exercises within a block are supersetted.
Expand Down Expand Up @@ -189,7 +216,7 @@ Add to your Claude Code `settings.json`, Cursor config, or any MCP client:
| `get_strength` | Current strength scores |
| `get_strength_history` | Strength progression |
| `get_profile` | User profile |
| `get_workout_history` | Recent workouts |
| `get_workout_history` | Recent activities, with strength eligibility and optional strength-only filtering |
| `get_workout_detail` | Per-set weights, 1RM, power, struggling scores |
| `get_performance_summary` | Formatted summary with L/R splits |
| `get_exercise_history` | Progressive overload tracking across sessions |
Expand All @@ -199,6 +226,18 @@ Add to your Claude Code `settings.json`, Cursor config, or any MCP client:
| `delete_workout` | Remove custom workout |
| `get_volume_report` | Training volume analysis |

`get_workout_history` accepts limits from 1 to 50 and enforces them locally
because Tonal currently returns a fixed 50-row activity page. Its response
includes `returned_count`, `available_in_page`, `requested_limit_satisfied`,
`source_page_exhausted`, and `upstream_page_may_be_truncated`.
`requested_limit_satisfied` means the requested number of matching rows was
returned; `source_page_exhausted` means Tonal returned fewer than 50 source
rows. Neither claims the upstream history is exhaustive. Volume and
exercise-history responses also include completeness metadata; when the flag
is false/true respectively, older activity may exist beyond the upstream page.
Volume reports fail completeness closed and expose
`unparseable_activity_count` when source timestamps are unusable.

## Token management

Tokens expire in ~10 hours. The refresh token lasts much longer.
Expand Down
44 changes: 43 additions & 1 deletion docs/api-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ Returns recent workout activities (Tonal sessions, external activities):
"activityId": "uuid",
"userId": "uuid",
"activityTime": "2026-04-11T18:33:21.38Z",
"activityType": "workout",
"activityType": "Internal",
"workoutPreview": {
"activityId": "uuid",
"workoutId": "uuid",
Expand All @@ -185,12 +185,33 @@ Returns recent workout activities (Tonal sessions, external activities):
]
```

`activityType` is `Internal` for Tonal strength workouts and `External` for
other logged activities. Only `Internal` activities have workout-detail and
formatted-summary records.

The live endpoint currently ignores common pagination inputs and returns a
fixed 50-row bare list. The MCP adapter enforces requested history limits
locally and reports page/completeness metadata rather than advertising an
unverified cursor or offset contract. `requested_limit_satisfied` reports
whether the requested number of matching activities was returned, while
`source_page_exhausted` reports whether the observed source page contained
fewer than 50 rows. Volume reports preserve the legacy `days` and `workouts`
fields as aliases, return a stable zero-valued schema, and set `is_complete`
to false when `unparseable_activity_count` is nonzero.

### Workout Detail (Raw Sets)
```
GET /v6/users/{userId}/workout-activities/{activityId}
```

Returns the full workout with per-set data. This is the richest data source.
The MCP detail tools convert detail-endpoint 404s into structured results:
`no_strength_data` for a known `External` activity, `detail_unavailable` for a
known `Internal` activity, and `activity_not_found` when the ID is absent from a
short or exhausted activity page. If the ID is absent from a full fixed 50-row
page, the result is `activity_type_unknown` with
`activity_lookup_complete: false` and `upstream_page_may_be_truncated: true`.
Each result includes the `activity_id` and HTTP `status`.

**Top-level fields:**
| Field | Type | Description |
Expand Down Expand Up @@ -239,12 +260,33 @@ Returns the full workout with per-set data. This is the richest data source.
| `beginTime` | string | ISO 8601 |
| `endTime` | string | ISO 8601 |

The CLI and MCP detail adapters exclude the reserved Rest movement ID
`00000000-0000-0000-0000-000000000005`. They normalize `-1` difficulty and
consistency sentinels to `null` without discarding legitimate zero scores on
performed sets. For explicitly unperformed sets, placeholder-zero difficulty,
consistency, and suggested-weight values are `null`; actual/count fields remain
numeric zeros and configured mode flags remain available.
Bilateral `StraightBar` load fields use total cable load (2x the upstream
per-arm/base load) consistently in CLI detail, MCP detail, and MCP exercise
history. Volume remains the upstream total and is not doubled.
Off-machine, duration-based movements expose actual `duration_sec`,
`prescribed_duration_sec`, and Tonal's separate `duration_based_rep_goal`;
per-set resistance-only measurements are `null`, and the movement is marked
with `on_machine: false`, `counts_reps: false`, and
`measurement_type: "duration"`. Per-movement volume aggregates stay numeric,
with zero representing no digital-resistance contribution. Top-level volume
also measures Tonal's digital resistance and excludes unmeasured bodyweight
work. `measurement_type` describes repetition-versus-duration counting, so a
timed on-machine movement may still include measured resistance.

### Formatted Workout Summary
```
GET /v6/formatted/users/{userId}/workout-summaries/{activityId}
```

Returns a pre-aggregated summary with movement names and per-movement totals. Includes left/right side splits for unilateral exercises.
Those split objects exist only on this formatted endpoint. The raw-detail tools
preserve upstream `movementSide` as-is and do not join the two endpoints.

**Response structure:**
```json
Expand Down
Loading