@trainheroic-unofficial/cli 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.mjs +21 -2
- package/package.json +6 -5
- package/skill/trainheroic-unofficial/SKILL.md +248 -0
- package/skill/trainheroic-unofficial/references/api-reference.md +1361 -0
- package/skill/trainheroic-unofficial/references/data-warehouse.md +127 -0
- package/skill/trainheroic-unofficial/references/workout-creation.md +310 -0
|
@@ -0,0 +1,1361 @@
|
|
|
1
|
+
# TrainHeroic Coach API Documentation
|
|
2
|
+
|
|
3
|
+
Base URL: `https://api.trainheroic.com`
|
|
4
|
+
|
|
5
|
+
## Authentication
|
|
6
|
+
|
|
7
|
+
**Login:** `POST https://apis.trainheroic.com/auth` (form post with email/password)
|
|
8
|
+
|
|
9
|
+
Returns:
|
|
10
|
+
|
|
11
|
+
```json
|
|
12
|
+
{
|
|
13
|
+
"id": 444862,
|
|
14
|
+
"api_token": "...",
|
|
15
|
+
"refresh_token": "...",
|
|
16
|
+
"api_ttl": 7712.75,
|
|
17
|
+
"scope": "athlete",
|
|
18
|
+
"role": "athlete",
|
|
19
|
+
"session_id": "..."
|
|
20
|
+
}
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
**Coach session:** The coach platform stores `session_id` in localStorage (`persist:trainheroic`) and uses it as the `session-token` header for all API calls.
|
|
24
|
+
|
|
25
|
+
**Athlete API token:** The `apis.trainheroic.com` login also returns an `api_token` which is used with the `api-token` header (used by `apis.trainheroic.com/user` endpoint).
|
|
26
|
+
|
|
27
|
+
**Headers:**
|
|
28
|
+
|
|
29
|
+
- `session-token: <session_id>` — used by coach platform (library, builder, coachapp)
|
|
30
|
+
- `api-token: <api_token>` — used by `apis.trainheroic.com` web app
|
|
31
|
+
- `content-type: application/json`
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## Coach Platform Subdomains
|
|
36
|
+
|
|
37
|
+
| Subdomain | Purpose |
|
|
38
|
+
| -------------------------- | -------------------------------------------- |
|
|
39
|
+
| `apis.trainheroic.com` | Login portal, athlete web dashboard |
|
|
40
|
+
| `coach.trainheroic.com` | Athletes/Teams admin (AngularJS) |
|
|
41
|
+
| `library.trainheroic.com` | Exercise/Session/Program library (React/MUI) |
|
|
42
|
+
| `builder.trainheroic.com` | Session template builder |
|
|
43
|
+
| `coachapp.trainheroic.com` | Program builder (calendar view) |
|
|
44
|
+
| `teams.trainheroic.com` | Team management |
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## Endpoints
|
|
49
|
+
|
|
50
|
+
### User / Auth
|
|
51
|
+
|
|
52
|
+
| Method | Endpoint | Description |
|
|
53
|
+
| ------ | ---------------------------------- | -------------------------------------------- |
|
|
54
|
+
| GET | `/user/simple` | Current user profile (simplified) |
|
|
55
|
+
| GET | `/v5/users` | Current user info |
|
|
56
|
+
| GET | `/v5/users/{id}` | User by ID |
|
|
57
|
+
| GET | `/v5/users/{id}/features` | Feature flags for user |
|
|
58
|
+
| GET | `/v5/headCoach` | Head coach info (org, license, trial status) |
|
|
59
|
+
| GET | `/v5/coaches/orgs` | Coach organizations |
|
|
60
|
+
| GET | `/v5/userAgreementTerms/hasAgreed` | TOS agreement status |
|
|
61
|
+
| GET | `/avatars/user/{id}` | User avatar (302 redirect to static image) |
|
|
62
|
+
|
|
63
|
+
#### `GET /user/simple` Response
|
|
64
|
+
|
|
65
|
+
```json
|
|
66
|
+
{
|
|
67
|
+
"id": 2771594,
|
|
68
|
+
"profileImg": "https://static.trainheroic.com/avatar-2025/J/avatar-JI.png",
|
|
69
|
+
"coverImg": "https://static.trainheroic.com/images/defaults/5.png",
|
|
70
|
+
"firstName": "jamon",
|
|
71
|
+
"lastName": "iberico",
|
|
72
|
+
"username": "neat.nest6259@fastmail.com",
|
|
73
|
+
"email": "neat.nest6259@fastmail.com",
|
|
74
|
+
"is_active": true,
|
|
75
|
+
"days_from_login": 0,
|
|
76
|
+
"roles": ["COACH", "TRIAL"],
|
|
77
|
+
"hasRole": { "COACH": 1, "TRIAL": 1 },
|
|
78
|
+
"org_id": 602402,
|
|
79
|
+
"mpEnabled": null,
|
|
80
|
+
"use_metric": false,
|
|
81
|
+
"fdhqUser": false,
|
|
82
|
+
"trial_days_remaining": 14
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
#### `GET /v5/headCoach` Response
|
|
87
|
+
|
|
88
|
+
```json
|
|
89
|
+
{
|
|
90
|
+
"id": 2771594,
|
|
91
|
+
"nameFirst": "jamon",
|
|
92
|
+
"nameLast": "iberico",
|
|
93
|
+
"profileImage": "...",
|
|
94
|
+
"orgId": 602402,
|
|
95
|
+
"orgName": "Jamon Fit",
|
|
96
|
+
"coachLicenseId": 10,
|
|
97
|
+
"nextBillingDate": "2026-03-24",
|
|
98
|
+
"isTrial": true,
|
|
99
|
+
"isMarketplaceEnabled": false,
|
|
100
|
+
"isExpiredTrial": false,
|
|
101
|
+
"daysLeftInTrial": 14,
|
|
102
|
+
"isFirstMobileLogin": true
|
|
103
|
+
}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
### Athletes
|
|
109
|
+
|
|
110
|
+
| Method | Endpoint | Description |
|
|
111
|
+
| ------ | ---------------------------------- | -------------------------------- |
|
|
112
|
+
| GET | `/v5/athletes` | List all athletes |
|
|
113
|
+
| POST | `/v5/emails/validate` | **Validate email addresses** |
|
|
114
|
+
| POST | `/v5/athletes/inviteToTeam` | **Invite athletes to team** |
|
|
115
|
+
| PUT | `/v5/athletes/archive` | **Archive athletes** |
|
|
116
|
+
| PUT | `/v5/athletes/{athleteId}/archive` | **Archive single athlete** |
|
|
117
|
+
| PUT | `/v5/athletes/restore` | **Restore (unarchive) athletes** |
|
|
118
|
+
|
|
119
|
+
#### `GET /v5/athletes` Response
|
|
120
|
+
|
|
121
|
+
```json
|
|
122
|
+
[
|
|
123
|
+
{
|
|
124
|
+
"id": 2771594,
|
|
125
|
+
"fullName": "iberico, jamon",
|
|
126
|
+
"firstName": "jamon",
|
|
127
|
+
"lastName": "iberico",
|
|
128
|
+
"email": "neat.nest6259@fastmail.com",
|
|
129
|
+
"useMetric": 0,
|
|
130
|
+
"teamCount": 0,
|
|
131
|
+
"daysSinceLastLogin": 0,
|
|
132
|
+
"groups": [],
|
|
133
|
+
"groupTitles": [],
|
|
134
|
+
"userTags": [],
|
|
135
|
+
"imageProfile": "...",
|
|
136
|
+
"canUserBeRemovedFromTeam": false,
|
|
137
|
+
"athleteType": ""
|
|
138
|
+
}
|
|
139
|
+
]
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
#### `POST /v5/emails/validate`
|
|
143
|
+
|
|
144
|
+
Validates email addresses before sending invites.
|
|
145
|
+
|
|
146
|
+
**Request body:**
|
|
147
|
+
|
|
148
|
+
```json
|
|
149
|
+
{ "emails": "user@example.com" }
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
**Response:** `["user@example.com"]` (array of valid emails)
|
|
153
|
+
|
|
154
|
+
#### `POST /v5/athletes/inviteToTeam`
|
|
155
|
+
|
|
156
|
+
Sends team invites to athletes via email.
|
|
157
|
+
|
|
158
|
+
**Request body:**
|
|
159
|
+
|
|
160
|
+
```json
|
|
161
|
+
{
|
|
162
|
+
"teamType": 0,
|
|
163
|
+
"teamId": 4677619,
|
|
164
|
+
"orgId": null,
|
|
165
|
+
"emails": ["user@example.com"],
|
|
166
|
+
"message": "Follow these steps and you'll be set up and ready to go!"
|
|
167
|
+
}
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
**Response:**
|
|
171
|
+
|
|
172
|
+
```json
|
|
173
|
+
{
|
|
174
|
+
"sent": ["user@example.com"],
|
|
175
|
+
"notSent": []
|
|
176
|
+
}
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
The invite dialog also supports:
|
|
180
|
+
|
|
181
|
+
- **1:1 invites** (direct coach-athlete pairing)
|
|
182
|
+
- **CSV upload** for bulk invitations
|
|
183
|
+
|
|
184
|
+
#### `PUT /v5/athletes/archive`
|
|
185
|
+
|
|
186
|
+
Archives athletes (removes from active roster but preserves data).
|
|
187
|
+
|
|
188
|
+
**Request body:**
|
|
189
|
+
|
|
190
|
+
```json
|
|
191
|
+
{ "athleteIds": [2771596] }
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
#### `PUT /v5/athletes/restore`
|
|
195
|
+
|
|
196
|
+
Restores previously archived athletes.
|
|
197
|
+
|
|
198
|
+
**Request body:**
|
|
199
|
+
|
|
200
|
+
```json
|
|
201
|
+
{ "athleteIds": [2771596] }
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
---
|
|
205
|
+
|
|
206
|
+
### Teams
|
|
207
|
+
|
|
208
|
+
| Method | Endpoint | Description |
|
|
209
|
+
| ------ | ----------------------------------------------------- | ----------------------------- |
|
|
210
|
+
| GET | `/1.0/coach/teams` | List coach teams |
|
|
211
|
+
| GET | `/1.0/coach/teams?page={n}&pageSize={n}&q={search}` | Paginated/searchable teams |
|
|
212
|
+
| POST | `/1.0/coach/team/createWithTitleAndCode` | **Create team** |
|
|
213
|
+
| GET | `/1.0/coach/team/allLicenseSubscribedTeams/{groupId}` | Teams subscribed to a program |
|
|
214
|
+
| GET | `/1.0/coach/programs/taggedByTeam/{groupId}?type=1` | Programs tagged to a team |
|
|
215
|
+
| GET | `/v5/teams/{teamId}` | Team details (full object) |
|
|
216
|
+
| GET | `/v5/teams/{teamId}/teamCodes` | **List team access codes** |
|
|
217
|
+
| POST | `/v5/teams/{teamId}/teamCodes` | **Create access code** |
|
|
218
|
+
| DELETE | `/v5/teamCodes/{codeId}` | **Delete access code** |
|
|
219
|
+
|
|
220
|
+
#### `POST /1.0/coach/team/createWithTitleAndCode`
|
|
221
|
+
|
|
222
|
+
Creates a new team. Automatically creates a team calendar (program).
|
|
223
|
+
|
|
224
|
+
**Request body:**
|
|
225
|
+
|
|
226
|
+
```json
|
|
227
|
+
{ "title": "Test Team" }
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
**Side effects:** Creating a team triggers loading of:
|
|
231
|
+
|
|
232
|
+
- `/3.0/coach/program/{newCalendarId}` — the auto-created team calendar
|
|
233
|
+
- `/1.0/coach/programs/edit/{calendarId}/{year}/{month}/{day}` — calendar edit view
|
|
234
|
+
- `/2.0/coach/calendar/summary/{calendarId}/{year}/{month}/{day}` — calendar summary
|
|
235
|
+
|
|
236
|
+
#### `GET /v5/teams/{teamId}/teamCodes` Response
|
|
237
|
+
|
|
238
|
+
```json
|
|
239
|
+
[
|
|
240
|
+
{
|
|
241
|
+
"id": 874576,
|
|
242
|
+
"code": "17731133064959",
|
|
243
|
+
"created_by": 2771594,
|
|
244
|
+
"date_created": "2026-03-10T03:28:26Z",
|
|
245
|
+
"date_modified": "2026-03-10T03:28:26Z",
|
|
246
|
+
"description": "",
|
|
247
|
+
"end_date": "2099-01-01T00:00:00Z",
|
|
248
|
+
"max_use_count": null,
|
|
249
|
+
"name": "",
|
|
250
|
+
"start_date": "2026-03-10T03:28:26Z",
|
|
251
|
+
"status": 1,
|
|
252
|
+
"team_id": 4677619,
|
|
253
|
+
"type": 2,
|
|
254
|
+
"use_count": 0
|
|
255
|
+
}
|
|
256
|
+
]
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
#### `POST /v5/teams/{teamId}/teamCodes`
|
|
260
|
+
|
|
261
|
+
Creates a new access code for athletes to join the team.
|
|
262
|
+
|
|
263
|
+
**Request body:**
|
|
264
|
+
|
|
265
|
+
```json
|
|
266
|
+
{ "type": 2 }
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
**Response:**
|
|
270
|
+
|
|
271
|
+
```json
|
|
272
|
+
{
|
|
273
|
+
"type": 2,
|
|
274
|
+
"team_id": 4677619,
|
|
275
|
+
"code": 1773116732,
|
|
276
|
+
"id": 874586,
|
|
277
|
+
"created_by": 2771594
|
|
278
|
+
}
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
#### `DELETE /v5/teamCodes/{codeId}`
|
|
282
|
+
|
|
283
|
+
Deletes an access code. No request body needed.
|
|
284
|
+
|
|
285
|
+
---
|
|
286
|
+
|
|
287
|
+
### Programs
|
|
288
|
+
|
|
289
|
+
| Method | Endpoint | Description |
|
|
290
|
+
| ------ | ------------------------------- | ------------------------------- |
|
|
291
|
+
| GET | `/v5/programs` | List programs |
|
|
292
|
+
| GET | `/v5/programs/new` | New programs |
|
|
293
|
+
| GET | `/v5/programs/free` | Free programs |
|
|
294
|
+
| GET | `/v5/programs/fixed` | Fixed programs |
|
|
295
|
+
| GET | `/1.0/coach/programs` | Coach programs list |
|
|
296
|
+
| GET | `/1.0/coach/programs/edit/{id}` | Program edit data |
|
|
297
|
+
| GET | `/3.0/coach/program/{id}` | Program detail (full structure) |
|
|
298
|
+
| GET | `/1.0/coach/subscriptions` | Program subscriptions |
|
|
299
|
+
|
|
300
|
+
#### `GET /3.0/coach/program/{id}` Response
|
|
301
|
+
|
|
302
|
+
```json
|
|
303
|
+
{
|
|
304
|
+
"id": 4713234,
|
|
305
|
+
"user_id": 2771594,
|
|
306
|
+
"published": 0,
|
|
307
|
+
"description": "",
|
|
308
|
+
"type": 2,
|
|
309
|
+
"length": 28,
|
|
310
|
+
"days": 0,
|
|
311
|
+
"title": "Test Program",
|
|
312
|
+
"group_id": 4677607,
|
|
313
|
+
"logo": "...",
|
|
314
|
+
"org_id": 602402
|
|
315
|
+
}
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
#### `GET /1.0/coach/programs` Response
|
|
319
|
+
|
|
320
|
+
```json
|
|
321
|
+
[
|
|
322
|
+
{
|
|
323
|
+
"id": 4677607,
|
|
324
|
+
"order": 0,
|
|
325
|
+
"owner_user_id": 2771594,
|
|
326
|
+
"featured": 0,
|
|
327
|
+
"title": "Test Program",
|
|
328
|
+
"date_created": 1773112569,
|
|
329
|
+
"description": "",
|
|
330
|
+
"slug": "iberico-program-1773112569",
|
|
331
|
+
"status": 0,
|
|
332
|
+
"group_program": 4713234,
|
|
333
|
+
"org_id": 602402,
|
|
334
|
+
"logo": "..."
|
|
335
|
+
}
|
|
336
|
+
]
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
---
|
|
340
|
+
|
|
341
|
+
### Exercises
|
|
342
|
+
|
|
343
|
+
| Method | Endpoint | Description |
|
|
344
|
+
| ------ | -------------------------------------------- | ----------------------------------------------- |
|
|
345
|
+
| GET | `/1.0/coach/exercises?page={n}&pageSize={n}` | Coach exercises (paginated) |
|
|
346
|
+
| GET | `/v5/exerciseLibrary/all` | Full exercise library (all available exercises) |
|
|
347
|
+
| POST | `/2.0/coach/exercise/create` | **Create exercise** |
|
|
348
|
+
| POST | `/2.0/coach/exercise/update/{exerciseId}` | **Update exercise** |
|
|
349
|
+
| DELETE | `/v5/exercises/{exerciseId}` | **Delete exercise** |
|
|
350
|
+
|
|
351
|
+
#### `POST /2.0/coach/exercise/create`
|
|
352
|
+
|
|
353
|
+
Creates a new custom exercise. See [Custom Exercise Creation](#custom-exercise-creation) section for full details and examples.
|
|
354
|
+
|
|
355
|
+
**Tags endpoint used during creation:** `GET /2.0/coach/tags/getByType/3` (exercise tags)
|
|
356
|
+
|
|
357
|
+
#### `POST /2.0/coach/exercise/update/{exerciseId}`
|
|
358
|
+
|
|
359
|
+
Updates a custom exercise. Takes the same fields as create. Response wraps the updated exercise in `{"success":1,"data":{...}}`.
|
|
360
|
+
|
|
361
|
+
#### `DELETE /v5/exercises/{exerciseId}`
|
|
362
|
+
|
|
363
|
+
Deletes a custom exercise. No request body needed. Only works on exercises where `can_edit: 1`.
|
|
364
|
+
|
|
365
|
+
---
|
|
366
|
+
|
|
367
|
+
### Sessions / Workouts
|
|
368
|
+
|
|
369
|
+
| Method | Endpoint | Description |
|
|
370
|
+
| ------ | ------------------------------------------------------------------------------------- | ----------------------------------------------------------------- |
|
|
371
|
+
| GET | `/1.0/coach/workouts?page={n}&pageSize={n}` | Session templates list |
|
|
372
|
+
| GET | `/2.0/coach/workoutSetExercise/template` | Prescription templates (sets/reps schemes) |
|
|
373
|
+
| POST | `/v5/sessions/template` | **Create session template** (library) |
|
|
374
|
+
| DELETE | `/v5/sessions/template/{sessionId}` | **Delete session template** |
|
|
375
|
+
| POST | `/2.0/coach/calendar/workout/createWorkoutForTimelineDay/{programId}/{day}/null` | **Create session in program** (timeline) |
|
|
376
|
+
| POST | `/2.0/coach/calendar/workout/createWorkoutForDay/{calendarId}/{year}/{month}/{day}/0` | **Create session on team calendar date** |
|
|
377
|
+
| POST | `/2.0/coach/calendar/saveProgramWorkoutSets` | **Add block to session** |
|
|
378
|
+
| POST | `/2.0/coach/calendar/saveWorkoutSetExercises` | **Add exercise to block** (with prescription) |
|
|
379
|
+
| POST | `/2.0/coach/calendar/programWorkout/publish` | **Publish session** |
|
|
380
|
+
| PUT | `/3.0/coach/workout/{workoutId}` | **Set session note** (Coach Instructions) / update programWorkout |
|
|
381
|
+
| GET | `/2.0/coach/calendar/summary/{calendarId}/{year}/{month}/{day}` | Calendar summary for date |
|
|
382
|
+
| GET | `/1.0/coach/programs/edit/{calendarId}/{year}/{month}/{day}` | Calendar edit view for date |
|
|
383
|
+
|
|
384
|
+
---
|
|
385
|
+
|
|
386
|
+
### Workout Creation Flow (Full Sequence)
|
|
387
|
+
|
|
388
|
+
The complete flow for creating a workout on a team calendar:
|
|
389
|
+
|
|
390
|
+
#### Step 1: Create Team (if needed)
|
|
391
|
+
|
|
392
|
+
```
|
|
393
|
+
POST /1.0/coach/team/createWithTitleAndCode
|
|
394
|
+
Body: { "title": "My Team" }
|
|
395
|
+
→ Returns team with calendar ID (e.g. 4713246)
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
#### Step 2: Create Session on Calendar Date
|
|
399
|
+
|
|
400
|
+
```
|
|
401
|
+
POST /2.0/coach/calendar/workout/createWorkoutForDay/{calendarId}/{year}/{month}/{day}/0
|
|
402
|
+
→ Creates empty session, returns workout data including workout_id
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
For program timelines (not date-based):
|
|
406
|
+
|
|
407
|
+
```
|
|
408
|
+
POST /2.0/coach/calendar/workout/createWorkoutForTimelineDay/{programId}/{day}/null
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
#### Step 3: Add Block to Session
|
|
412
|
+
|
|
413
|
+
```
|
|
414
|
+
POST /2.0/coach/calendar/saveProgramWorkoutSets
|
|
415
|
+
Body:
|
|
416
|
+
[{
|
|
417
|
+
"workout_id": 140318787,
|
|
418
|
+
"order": 1,
|
|
419
|
+
"type": 4,
|
|
420
|
+
"instruction": "",
|
|
421
|
+
"is_redzone": null,
|
|
422
|
+
"redzone_type": 0,
|
|
423
|
+
"exercises": [],
|
|
424
|
+
"exerciseKeys": [],
|
|
425
|
+
"key": "k::9292",
|
|
426
|
+
"title": "Strength/Power"
|
|
427
|
+
}]
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
Block types (observed `type` field):
|
|
431
|
+
|
|
432
|
+
- `1` = Conditioning
|
|
433
|
+
- `2` = Hypertrophy
|
|
434
|
+
- `4` = Strength/Power (default)
|
|
435
|
+
|
|
436
|
+
The `title` field can be set to any custom string. The `instruction` field provides block-level coach notes.
|
|
437
|
+
|
|
438
|
+
#### Step 4: Add Exercise(s) to Block (with prescription)
|
|
439
|
+
|
|
440
|
+
```
|
|
441
|
+
POST /2.0/coach/calendar/saveWorkoutSetExercises
|
|
442
|
+
Body: [exercise1, exercise2, ...] // array of exercises
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
> **Important:** This endpoint requires additional fields beyond the obvious ones (`set_num`, `key`, `setKey`, `eType`, all 10 param slots, etc.) or it returns 500. See [Required Fields for saveWorkoutSetExercises](#required-fields-for-saveworkoutsetexercises) for the complete field list.
|
|
446
|
+
|
|
447
|
+
**Single exercise:**
|
|
448
|
+
|
|
449
|
+
```json
|
|
450
|
+
[
|
|
451
|
+
{
|
|
452
|
+
"exercise_id": 1,
|
|
453
|
+
"workout_set_id": 671133862,
|
|
454
|
+
"set_id": 671133862,
|
|
455
|
+
"title": "Back Squat",
|
|
456
|
+
"instruction": "",
|
|
457
|
+
"order": 1,
|
|
458
|
+
"param_1_type": 3,
|
|
459
|
+
"param_2_type": 1,
|
|
460
|
+
"param_1_data_1": "5",
|
|
461
|
+
"param_1_data_2": "5",
|
|
462
|
+
"param_1_data_3": "5",
|
|
463
|
+
"param_1_data_4": "",
|
|
464
|
+
"param_1_data_5": "",
|
|
465
|
+
"param_1_data_6": "",
|
|
466
|
+
"param_1_data_7": "",
|
|
467
|
+
"param_1_data_8": "",
|
|
468
|
+
"param_1_data_9": "",
|
|
469
|
+
"param_1_data_10": "",
|
|
470
|
+
"param_2_data_1": "225",
|
|
471
|
+
"param_2_data_2": "245",
|
|
472
|
+
"param_2_data_3": "265",
|
|
473
|
+
"param_2_data_4": "",
|
|
474
|
+
"param_2_data_5": "",
|
|
475
|
+
"param_2_data_6": "",
|
|
476
|
+
"param_2_data_7": "",
|
|
477
|
+
"param_2_data_8": "",
|
|
478
|
+
"param_2_data_9": "",
|
|
479
|
+
"param_2_data_10": "",
|
|
480
|
+
"workout_set_exercise_template_id": null,
|
|
481
|
+
"no_sets": 0,
|
|
482
|
+
"param_count": 3,
|
|
483
|
+
"set_num": 3,
|
|
484
|
+
"key": "k::1001",
|
|
485
|
+
"setKey": 671133862,
|
|
486
|
+
"video_url": "",
|
|
487
|
+
"thumbnail_url": "",
|
|
488
|
+
"tags": [],
|
|
489
|
+
"eType": "e",
|
|
490
|
+
"use_count": 0
|
|
491
|
+
}
|
|
492
|
+
]
|
|
493
|
+
```
|
|
494
|
+
|
|
495
|
+
**Superset (multiple exercises in same block):**
|
|
496
|
+
Send multiple exercises in the same array, all with the same `workout_set_id`/`set_id` but different `order` values:
|
|
497
|
+
|
|
498
|
+
```json
|
|
499
|
+
[
|
|
500
|
+
{ "exercise_id": 1, "workout_set_id": 671133862, "order": 1, "title": "Back Squat", ... },
|
|
501
|
+
{ "exercise_id": 3, "workout_set_id": 671133862, "order": 2, "title": "Front Squat", ... }
|
|
502
|
+
]
|
|
503
|
+
```
|
|
504
|
+
|
|
505
|
+
This creates A1: Back Squat, A2: Front Squat displayed as a superset.
|
|
506
|
+
|
|
507
|
+
**Drop set pattern (decreasing weight, increasing reps):**
|
|
508
|
+
|
|
509
|
+
```json
|
|
510
|
+
{
|
|
511
|
+
"exercise_id": 1162,
|
|
512
|
+
"title": "Bench Press",
|
|
513
|
+
"instruction": "Drop set - no rest between sets",
|
|
514
|
+
"param_1_type": 3,
|
|
515
|
+
"param_2_type": 1,
|
|
516
|
+
"param_1_data_1": "6",
|
|
517
|
+
"param_2_data_1": "185",
|
|
518
|
+
"param_1_data_2": "8",
|
|
519
|
+
"param_2_data_2": "165",
|
|
520
|
+
"param_1_data_3": "10",
|
|
521
|
+
"param_2_data_3": "145",
|
|
522
|
+
"param_1_data_4": "12",
|
|
523
|
+
"param_2_data_4": "125",
|
|
524
|
+
"param_1_data_5": "15",
|
|
525
|
+
"param_2_data_5": "95",
|
|
526
|
+
"param_count": 5
|
|
527
|
+
}
|
|
528
|
+
```
|
|
529
|
+
|
|
530
|
+
**Parameter types (param_1_type / param_2_type):**
|
|
531
|
+
| Value | Type | Display |
|
|
532
|
+
|-------|------|---------|
|
|
533
|
+
| 0 | None | (no parameter) |
|
|
534
|
+
| 1 | Weight | `225 lb` |
|
|
535
|
+
| 2 | Weight (% of max) | `75%` |
|
|
536
|
+
| 3 | Reps | `5` |
|
|
537
|
+
| 4 | Time (seconds) | `1:00` |
|
|
538
|
+
| 5 | Distance (yards) | `50yd` |
|
|
539
|
+
| 6 | Distance (meters) | `50m` |
|
|
540
|
+
| 7 | Height | `inches` |
|
|
541
|
+
| 10 | Distance (miles) | miles |
|
|
542
|
+
| 11 | Distance (feet) | feet |
|
|
543
|
+
| 12 | Height (inches) | inches |
|
|
544
|
+
| 13 | Heart Rate | bpm |
|
|
545
|
+
| 14 | RPE | rating |
|
|
546
|
+
| 18 | Time (seconds, alt) | `0:30` |
|
|
547
|
+
|
|
548
|
+
> **The unit is fixed per exercise — you cannot set it at prescribe time (verified).**
|
|
549
|
+
> On save the API discards the `param_1_type`/`param_2_type` you send and restores
|
|
550
|
+
> the exercise's library defaults. The `param_*_data_N` _values_ are kept, so a
|
|
551
|
+
> value sent under the wrong assumed unit renders under the exercise's real unit.
|
|
552
|
+
>
|
|
553
|
+
> - **`param_1_type` (primary) is always forced to the exercise default.** e.g. stock
|
|
554
|
+
> `Run` (id 82) is `10` (miles); sending `6` (meters) is ignored and `200` shows as
|
|
555
|
+
> _200 miles_. To program meters, pick a meters-native exercise (`Sprint` 127,
|
|
556
|
+
> `Rowing` 101, `Shuttle Sprint` 42523) or a custom exercise — there is no metric
|
|
557
|
+
> "Run". `$TH exercise resolve` now prints a `units` array (ordered by entry slot,
|
|
558
|
+
> `[param 1, param 2]`); check it.
|
|
559
|
+
> - **`param_2_type` (secondary) is forced to the default too, with one exception:**
|
|
560
|
+
> if the exercise has no secondary param (default `0`/none) you may add weight
|
|
561
|
+
> (`1`) — this is how weighted Pull-Ups/Dips work. You cannot swap an exercise's
|
|
562
|
+
> existing secondary unit, and **`2` (% of max) and `14` (RPE) never stick on a
|
|
563
|
+
> weight-default lift — both coerce to weight, so the numbers render as pounds.**
|
|
564
|
+
> Put % or RPE in the `instruction` text instead.
|
|
565
|
+
> - the workout builder reads the local exercise cache and prints a `WARNING` when a
|
|
566
|
+
> sent param type will be overridden.
|
|
567
|
+
|
|
568
|
+
**Common param type combos (from 2067 exercises):**
|
|
569
|
+
|
|
570
|
+
- `p1=3, p2=None` — Reps only (801 exercises, e.g. Plyo Lunge, Lateral Lunge)
|
|
571
|
+
- `p1=3, p2=1` — Reps @ Weight (619 exercises, e.g. Back Squat, Bench Press, Deadlift)
|
|
572
|
+
- `p1=3, p2=0` — Reps only (170 exercises, e.g. Push-Up, Pull-Up, Burpee, Air Squat)
|
|
573
|
+
- `p1=5, p2=None` — Distance/yards only (133 exercises, e.g. Sled Push, Bear Crawl)
|
|
574
|
+
- `p1=4, p2=None` — Time only (91 exercises, e.g. Jog)
|
|
575
|
+
- `p1=3, p2=4` — Reps @ Time (51 exercises, e.g. L Drill)
|
|
576
|
+
- `p1=4, p2=0` — Time only (37 exercises, e.g. Plank, Rest)
|
|
577
|
+
- `p1=5, p2=1` — Distance @ Weight (11 exercises, e.g. Sled Drags)
|
|
578
|
+
- `p1=10, p2=4` — Miles @ Time (3 exercises, e.g. Run, Walk)
|
|
579
|
+
|
|
580
|
+
**Fields:**
|
|
581
|
+
|
|
582
|
+
- `param_1_data_N` = Value for param 1 in set N (1-10 max)
|
|
583
|
+
- `param_2_data_N` = Value for param 2 in set N (1-10 max)
|
|
584
|
+
- `param_count` = Number of sets
|
|
585
|
+
- `no_sets` = 0 (normal), non-zero may indicate special handling
|
|
586
|
+
- `instruction` = Per-exercise coach notes
|
|
587
|
+
|
|
588
|
+
**Note:** The API ignores the `title` field and uses the exercise's real title from `exercise_id`. It also overrides **both** `param_1_type` and `param_2_type` to the exercise's library defaults (see the fixed-unit note above) — the data values you send are kept, but the unit is the exercise's, not the one you requested.
|
|
589
|
+
|
|
590
|
+
#### Step 5: Publish Session
|
|
591
|
+
|
|
592
|
+
```
|
|
593
|
+
POST /2.0/coach/calendar/programWorkout/publish
|
|
594
|
+
Body: [142002657] // array of programWorkout IDs
|
|
595
|
+
```
|
|
596
|
+
|
|
597
|
+
#### (Optional) Set the session note — Coach Instructions
|
|
598
|
+
|
|
599
|
+
The **session-level** instruction (the day-note shown at the top of a session: a
|
|
600
|
+
greeting + workout writeup) is **not** the same as a block's `instruction`. It is
|
|
601
|
+
set with a PUT to the programWorkout, using the same `workout_id` returned at
|
|
602
|
+
create time:
|
|
603
|
+
|
|
604
|
+
```
|
|
605
|
+
PUT /3.0/coach/workout/{workout_id}
|
|
606
|
+
Body: <the full programWorkout object, with `instruction` set>
|
|
607
|
+
```
|
|
608
|
+
|
|
609
|
+
Build the body from the session object you already have — the create-time response,
|
|
610
|
+
or a day's entry from `GET /1.0/coach/programs/edit/{cal}/{y}/{m}/{d}` — then set
|
|
611
|
+
`instruction` and replace `sets`/`setKeys`:
|
|
612
|
+
|
|
613
|
+
```json
|
|
614
|
+
{
|
|
615
|
+
"id": 151122476, // programWorkout id
|
|
616
|
+
"workout_id": 149544173,
|
|
617
|
+
"program_id": 2039741,
|
|
618
|
+
"date": "2026-06-22",
|
|
619
|
+
"day": 22,
|
|
620
|
+
"month": 6,
|
|
621
|
+
"year": 2026,
|
|
622
|
+
"title": "2026-6-22",
|
|
623
|
+
"type": 4,
|
|
624
|
+
"session": 0,
|
|
625
|
+
"timeline_day": 0,
|
|
626
|
+
"published": null,
|
|
627
|
+
"program_type": 0,
|
|
628
|
+
"logo": "...",
|
|
629
|
+
"team_logo": null,
|
|
630
|
+
"program_title": "...",
|
|
631
|
+
"team_title": "...",
|
|
632
|
+
"deleted": null,
|
|
633
|
+
"group_team_subscription_id": null,
|
|
634
|
+
"date_rescheduled": null,
|
|
635
|
+
"sets": [715883274, 715883275, 715883276], // block ids as a LIST (see caveat)
|
|
636
|
+
"setKeys": [715883274, 715883275, 715883276],
|
|
637
|
+
"instruction": "Welcome to Week 12...\n\n----\n\nFind a 1RM Strict Press..."
|
|
638
|
+
}
|
|
639
|
+
```
|
|
640
|
+
|
|
641
|
+
Returns `200`. Notes (verified):
|
|
642
|
+
|
|
643
|
+
- **`sets`/`setKeys` must be a flat list of block ids**, sorted by each block's
|
|
644
|
+
`order`. The edit-GET returns `sets` as a **dict keyed by block id** — convert it.
|
|
645
|
+
- **Setting `instruction` does not publish.** `published` is echoed back exactly as
|
|
646
|
+
sent (e.g. `null` for a draft), so send the session's current state and set the
|
|
647
|
+
note _before_ publishing if it should stay a draft.
|
|
648
|
+
- Also carries `title`/`type`/`published` — this is the general programWorkout
|
|
649
|
+
update, not an instruction-only endpoint.
|
|
650
|
+
- Dead ends: `POST /2.0/coach/calendar/saveProgramWorkout` is a 404, and passing
|
|
651
|
+
`instruction` in the `createWorkoutForDay` body is ignored — this PUT is the path.
|
|
652
|
+
|
|
653
|
+
the workout builder does this automatically: add a top-level `"instruction"` to the
|
|
654
|
+
spec and it PUTs after the blocks are saved (before publish). Read it back with
|
|
655
|
+
`--read` — the session note prints under "Coach Instructions".
|
|
656
|
+
|
|
657
|
+
---
|
|
658
|
+
|
|
659
|
+
#### `POST /v5/sessions/template`
|
|
660
|
+
|
|
661
|
+
Creates a reusable session template in the library.
|
|
662
|
+
|
|
663
|
+
**Request body:**
|
|
664
|
+
|
|
665
|
+
- `title` (required)
|
|
666
|
+
- `instructions` (optional)
|
|
667
|
+
- Blocks with exercises can be added
|
|
668
|
+
|
|
669
|
+
#### `GET /2.0/coach/workoutSetExercise/template` Response (prescription templates)
|
|
670
|
+
|
|
671
|
+
```json
|
|
672
|
+
[
|
|
673
|
+
{
|
|
674
|
+
"id": 99,
|
|
675
|
+
"title": "4 x 3",
|
|
676
|
+
"type": 1,
|
|
677
|
+
"param_1_type": 3,
|
|
678
|
+
"param_2_type": 0,
|
|
679
|
+
"param_1_data_1": "3",
|
|
680
|
+
"param_1_data_2": "3",
|
|
681
|
+
"param_1_data_3": "3",
|
|
682
|
+
"param_1_data_4": "3",
|
|
683
|
+
"param_count": 4,
|
|
684
|
+
"tags": [{ "id": 251, "title": "Strength", "type": 4 }],
|
|
685
|
+
"editable": false,
|
|
686
|
+
"use_count": 0
|
|
687
|
+
}
|
|
688
|
+
]
|
|
689
|
+
```
|
|
690
|
+
|
|
691
|
+
---
|
|
692
|
+
|
|
693
|
+
### Tags
|
|
694
|
+
|
|
695
|
+
| Method | Endpoint | Description |
|
|
696
|
+
| ------ | ------------------------------- | -------------------- |
|
|
697
|
+
| GET | `/2.0/coach/tags/getByType/3` | Exercise tags |
|
|
698
|
+
| GET | `/2.0/coach/tags/getByType/4` | Training effect tags |
|
|
699
|
+
| GET | `/2.0/coach/tags/getSportsTags` | Sports tags |
|
|
700
|
+
|
|
701
|
+
**Tag types:**
|
|
702
|
+
|
|
703
|
+
- Type 3: Exercise category (Olympic Lifts, Primary Lifts, Accessory Lifts, Gymnastics, etc.)
|
|
704
|
+
- Type 4: Training effect (Strength, Hypertrophy, Power, Conditioning, etc.)
|
|
705
|
+
|
|
706
|
+
#### `GET /2.0/coach/tags/getByType/{type}` Response
|
|
707
|
+
|
|
708
|
+
```json
|
|
709
|
+
{
|
|
710
|
+
"success": 1,
|
|
711
|
+
"data": {
|
|
712
|
+
"tagType": { "id": "3", "title": "Exercise" },
|
|
713
|
+
"tags": [
|
|
714
|
+
{ "id": 211, "title": "Olympic Lifts", "type": 3, "use_count": 28056 },
|
|
715
|
+
{ "id": 212, "title": "Primary Lifts", "type": 3, "use_count": 33280 },
|
|
716
|
+
{ "id": 213, "title": "Accessory Lifts", "type": 3, "use_count": 171716 }
|
|
717
|
+
]
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
```
|
|
721
|
+
|
|
722
|
+
---
|
|
723
|
+
|
|
724
|
+
### Favorites / Preferences
|
|
725
|
+
|
|
726
|
+
| Method | Endpoint | Description |
|
|
727
|
+
| ------ | ---------------------------------- | ----------------- |
|
|
728
|
+
| GET | `/2.0/coach/favorite?pageSize={n}` | Favorited items |
|
|
729
|
+
| GET | `/2.0/coach/prefs` | Coach preferences |
|
|
730
|
+
|
|
731
|
+
#### `GET /2.0/coach/prefs` Response
|
|
732
|
+
|
|
733
|
+
```json
|
|
734
|
+
{
|
|
735
|
+
"id": 2800633,
|
|
736
|
+
"email_workout_preview": 1,
|
|
737
|
+
"email_new_posts": 0,
|
|
738
|
+
"email_coach_posts": 1,
|
|
739
|
+
"mobile_workout_preview": 1,
|
|
740
|
+
"auto_update_wm": 0,
|
|
741
|
+
"show_exercises_trainheroic": 1,
|
|
742
|
+
"show_programs_trainheroic": 1,
|
|
743
|
+
"show_calendars_trainheroic": 1,
|
|
744
|
+
"show_workout_set_exercise_templates_trainheroic": 1,
|
|
745
|
+
"survey_team_enabled": 1,
|
|
746
|
+
"survey_one_to_one_enabled": 1
|
|
747
|
+
}
|
|
748
|
+
```
|
|
749
|
+
|
|
750
|
+
---
|
|
751
|
+
|
|
752
|
+
### Messaging
|
|
753
|
+
|
|
754
|
+
Chat between a coach and athletes/teams. A **stream** is a conversation; a
|
|
755
|
+
**comment** is a message in it. Verified against a live
|
|
756
|
+
account (see `$TH message`).
|
|
757
|
+
|
|
758
|
+
| Method | Endpoint | Description |
|
|
759
|
+
| ------ | -------------------------------------------------------------- | ------------------------------------------------------------------------------ |
|
|
760
|
+
| GET | `/v5/messaging/streams` | List conversations (bucketed) |
|
|
761
|
+
| GET | `/v5/messaging/streams/{streamId}/comments?lastCommentId={id}` | Messages in a stream; `lastCommentId` returns only comments newer than that id |
|
|
762
|
+
| POST | `/v5/messaging/streams/{streamId}/comments` | **Send a message** (athlete-facing, immediate) |
|
|
763
|
+
| DELETE | `/v5/messaging/streams/{streamId}/comments/{commentId}` | **Delete a message** (soft delete) |
|
|
764
|
+
| GET | `/v5/messaging/reactions` | Reaction catalog (Like/Love/Fire/Trophy/…) |
|
|
765
|
+
| GET | `/v5/notifications/counts` | Unread message counts (cheap "anything new?" poll) |
|
|
766
|
+
|
|
767
|
+
#### `GET /v5/messaging/streams` Response
|
|
768
|
+
|
|
769
|
+
Conversations grouped into four buckets. Each entry's `id` is the **stream id**
|
|
770
|
+
(distinct from `teamId`/`userId`) used by every other messaging call.
|
|
771
|
+
|
|
772
|
+
```json
|
|
773
|
+
{
|
|
774
|
+
"teams": [
|
|
775
|
+
{
|
|
776
|
+
"id": 37731134,
|
|
777
|
+
"teamId": 4945224,
|
|
778
|
+
"title": "Test Team",
|
|
779
|
+
"isOwner": true,
|
|
780
|
+
"logo": "...",
|
|
781
|
+
"lastViewed": 1781808863
|
|
782
|
+
}
|
|
783
|
+
],
|
|
784
|
+
"athletes": [
|
|
785
|
+
{
|
|
786
|
+
"id": 37730920,
|
|
787
|
+
"userId": 2855688,
|
|
788
|
+
"teamId": 4945209,
|
|
789
|
+
"title": "[Demo] Kyle Jones",
|
|
790
|
+
"metadata": { "nameFirst": "...", "nameLast": "..." },
|
|
791
|
+
"logo": "..."
|
|
792
|
+
}
|
|
793
|
+
],
|
|
794
|
+
"programs": [],
|
|
795
|
+
"coaches": []
|
|
796
|
+
}
|
|
797
|
+
```
|
|
798
|
+
|
|
799
|
+
#### `GET /v5/messaging/streams/{streamId}/comments?lastCommentId={id}` Response
|
|
800
|
+
|
|
801
|
+
Array of comments, oldest→newest. Pass the highest `id` you've stored as
|
|
802
|
+
`lastCommentId` to fetch only newer ones (verified: returns `[]` when none are
|
|
803
|
+
newer). A blank `lastCommentId=` returns the whole stream.
|
|
804
|
+
|
|
805
|
+
```json
|
|
806
|
+
[
|
|
807
|
+
{
|
|
808
|
+
"id": 125652586,
|
|
809
|
+
"timestamp": 1781809398,
|
|
810
|
+
"content": "Nice work today!",
|
|
811
|
+
"imageUrl": null,
|
|
812
|
+
"thumbnailUrl": null,
|
|
813
|
+
"authorName": "A Cohen",
|
|
814
|
+
"authorLogo": "https://static.trainheroic.com/avatar-2025/A/avatar-AC.png",
|
|
815
|
+
"replies": [],
|
|
816
|
+
"reactions": [],
|
|
817
|
+
"isAuthor": true
|
|
818
|
+
}
|
|
819
|
+
]
|
|
820
|
+
```
|
|
821
|
+
|
|
822
|
+
- `isAuthor: false` is a message you **received**; `true` is one you sent.
|
|
823
|
+
- `id` doubles as the incremental cursor (`lastCommentId`).
|
|
824
|
+
- `replies[]` holds threaded replies as nested comment objects; `reactions[]`
|
|
825
|
+
holds reactions on the comment (decode via `/v5/messaging/reactions`).
|
|
826
|
+
|
|
827
|
+
#### `POST /v5/messaging/streams/{streamId}/comments` (send)
|
|
828
|
+
|
|
829
|
+
Returns the created comment (same shape as above). **The required field the
|
|
830
|
+
obvious guesses miss is `feed_id`** — the stream id repeated in the body.
|
|
831
|
+
`{ "content": "..." }` alone returns `400 Invalid parameters`; the full body the
|
|
832
|
+
web client sends is:
|
|
833
|
+
|
|
834
|
+
```json
|
|
835
|
+
{
|
|
836
|
+
"type": 0,
|
|
837
|
+
"content": "Nice work today!",
|
|
838
|
+
"photo_url": "",
|
|
839
|
+
"photoUrl": "",
|
|
840
|
+
"access_level": 0,
|
|
841
|
+
"parent_feed_item_id": null,
|
|
842
|
+
"feed_id": 37730920
|
|
843
|
+
}
|
|
844
|
+
```
|
|
845
|
+
|
|
846
|
+
- `feed_id` = the `{streamId}` from the path (required).
|
|
847
|
+
- `parent_feed_item_id` = a comment id to post a threaded reply; `null` for a
|
|
848
|
+
top-level message.
|
|
849
|
+
- Trimming any of the other fields also returns `400 Invalid parameters` —
|
|
850
|
+
send the whole body.
|
|
851
|
+
- There is **no server-side draft state**: a POST is delivered immediately.
|
|
852
|
+
|
|
853
|
+
#### `DELETE /v5/messaging/streams/{streamId}/comments/{commentId}`
|
|
854
|
+
|
|
855
|
+
Soft delete (sets `deleted_at`). No body. Returns the underlying row, which
|
|
856
|
+
reveals a comment can attach to more than chat:
|
|
857
|
+
|
|
858
|
+
```json
|
|
859
|
+
{
|
|
860
|
+
"id": 125652586,
|
|
861
|
+
"created_by": 2855687,
|
|
862
|
+
"type": 0,
|
|
863
|
+
"access_level": 0,
|
|
864
|
+
"content": "...",
|
|
865
|
+
"photo_url": "",
|
|
866
|
+
"deleted_at": "2026-06-18T19:05:35.000000Z",
|
|
867
|
+
"parent_feed_item_id": null,
|
|
868
|
+
"program_workout_id": null,
|
|
869
|
+
"saved_workout_set_id": null,
|
|
870
|
+
"group_id": null,
|
|
871
|
+
"saved_workout_id": null
|
|
872
|
+
}
|
|
873
|
+
```
|
|
874
|
+
|
|
875
|
+
#### `GET /v5/notifications/counts` Response
|
|
876
|
+
|
|
877
|
+
The cheap gate before fanning out across streams:
|
|
878
|
+
|
|
879
|
+
```json
|
|
880
|
+
{
|
|
881
|
+
"countNotViewed": 0,
|
|
882
|
+
"countNotificationNotViewed": 0,
|
|
883
|
+
"countMessagingNotViewed": 0,
|
|
884
|
+
"messaging": { "countDirectNotViewed": 0, "countTeamNotViewed": 0 }
|
|
885
|
+
}
|
|
886
|
+
```
|
|
887
|
+
|
|
888
|
+
#### Real-time channel (not needed for a sync)
|
|
889
|
+
|
|
890
|
+
The web client receives live messages over a separate long-poll channel —
|
|
891
|
+
`adapter.trainheroic.com/messaging?timestamp={ms}` (global) and
|
|
892
|
+
`adapter.trainheroic.com/messaging/team/{teamId}?timestamp={ms}` (per team),
|
|
893
|
+
loaded as a cross-origin iframe stream. A coach-side **sync does not need it**:
|
|
894
|
+
polling the REST `comments` endpoint with `lastCommentId` captures the same
|
|
895
|
+
messages. The entire chat UI is served from `adapter.trainheroic.com` and embedded
|
|
896
|
+
in `coachapp.trainheroic.com/messaging` as the `messagingHub` iframe.
|
|
897
|
+
|
|
898
|
+
---
|
|
899
|
+
|
|
900
|
+
### Coach Admin
|
|
901
|
+
|
|
902
|
+
| Method | Endpoint | Description |
|
|
903
|
+
| ------ | ------------------------------------------ | ------------------------- |
|
|
904
|
+
| GET | `/2.0/coach/admin/coachAthletes/{coachId}` | Athletes managed by coach |
|
|
905
|
+
| GET | `/user/mobile` | User mobile info |
|
|
906
|
+
|
|
907
|
+
---
|
|
908
|
+
|
|
909
|
+
### Analytics
|
|
910
|
+
|
|
911
|
+
| Method | Endpoint | Description |
|
|
912
|
+
| ------ | ---------------------------------------------- | ---------------------------------------------------------- |
|
|
913
|
+
| GET | `/v5/analytics` | Analytics categories (lists all available analytics types) |
|
|
914
|
+
| POST | `/v5/analytics/readiness/teams` | **Readiness survey — team** |
|
|
915
|
+
| POST | `/v5/analytics/readiness/users` | **Readiness survey — single athlete** |
|
|
916
|
+
| POST | `/v5/analytics/lift-one-rep-max-history/users` | **1RM history — single athlete** |
|
|
917
|
+
| POST | `/v5/analytics/training-summary/users` | **Training summary — single athlete** |
|
|
918
|
+
| POST | `/v5/analytics/training-summary/teams` | **Training summary — team** |
|
|
919
|
+
| POST | `/v5/analytics/compliance` | **Compliance data** |
|
|
920
|
+
| POST | `/v5/analytics/lift-progress/teams` | **Lift progress — team** |
|
|
921
|
+
| POST | `/v5/analytics/working-max-history/users` | **Working max history** |
|
|
922
|
+
|
|
923
|
+
#### `GET /v5/analytics` Response
|
|
924
|
+
|
|
925
|
+
Returns all available analytics categories and their instances:
|
|
926
|
+
|
|
927
|
+
```json
|
|
928
|
+
{
|
|
929
|
+
"categories": [
|
|
930
|
+
{
|
|
931
|
+
"key": "readiness",
|
|
932
|
+
"title": "Readiness",
|
|
933
|
+
"instances": ["readiness-survey-athlete", "readiness-survey-team"]
|
|
934
|
+
},
|
|
935
|
+
{
|
|
936
|
+
"key": "performance",
|
|
937
|
+
"title": "Performance",
|
|
938
|
+
"instances": ["lift-one-rep-max-history", "lift-one-rep-max-team-history"]
|
|
939
|
+
},
|
|
940
|
+
{
|
|
941
|
+
"key": "liftHistory",
|
|
942
|
+
"title": "Lift History",
|
|
943
|
+
"instances": ["lift-history-complete", "working-max-history"]
|
|
944
|
+
},
|
|
945
|
+
{ "key": "trainingSummary", "title": "Training Summary", "instances": ["training-summary"] },
|
|
946
|
+
{ "key": "compliance", "title": "Compliance", "instances": ["compliance-team"] },
|
|
947
|
+
{ "key": "liftProgress", "title": "Lift Progress", "instances": ["lift-progress-team"] }
|
|
948
|
+
]
|
|
949
|
+
}
|
|
950
|
+
```
|
|
951
|
+
|
|
952
|
+
#### `POST /v5/analytics/readiness/teams`
|
|
953
|
+
|
|
954
|
+
**Request body:**
|
|
955
|
+
|
|
956
|
+
```json
|
|
957
|
+
{ "teamId": 4677619, "date": "2026-03-09" }
|
|
958
|
+
```
|
|
959
|
+
|
|
960
|
+
**Response:** Returns columns (user_id, name_last, name_first, date_completed, sleep, mood, energy, stress, soreness, readiness) and rows of athlete readiness data.
|
|
961
|
+
|
|
962
|
+
#### `POST /v5/analytics/lift-one-rep-max-history/users`
|
|
963
|
+
|
|
964
|
+
**Request body:**
|
|
965
|
+
|
|
966
|
+
```json
|
|
967
|
+
{
|
|
968
|
+
"date_start": "2026-02-07",
|
|
969
|
+
"date_end": "2026-03-09",
|
|
970
|
+
"user_ids": ["2771596"],
|
|
971
|
+
"exercise_id": "424",
|
|
972
|
+
"use_metric": false
|
|
973
|
+
}
|
|
974
|
+
```
|
|
975
|
+
|
|
976
|
+
**Response:** Returns columns (exercise_title, user_id, name, date, estimated_1rm, etc.) and rows of 1RM history data.
|
|
977
|
+
|
|
978
|
+
---
|
|
979
|
+
|
|
980
|
+
### Notifications / Misc
|
|
981
|
+
|
|
982
|
+
| Method | Endpoint | Description |
|
|
983
|
+
| ------ | -------------------------- | ------------------------- |
|
|
984
|
+
| GET | `/v5/notifications` | Notifications list |
|
|
985
|
+
| GET | `/v5/notifications/counts` | Notification counts |
|
|
986
|
+
| GET | `/v5/site-banners` | Site banners |
|
|
987
|
+
| POST | `/v5/telemetry/track` | Telemetry/tracking events |
|
|
988
|
+
|
|
989
|
+
---
|
|
990
|
+
|
|
991
|
+
## Athlete API Endpoints (Mobile App)
|
|
992
|
+
|
|
993
|
+
These are used by the mobile app / athlete-facing client (documented in `train-heroic-schema.yml`):
|
|
994
|
+
|
|
995
|
+
| Method | Endpoint | Description |
|
|
996
|
+
| ------ | ------------------------------------------------------- | ---------------------------------------------- |
|
|
997
|
+
| POST | `/auth` | Login |
|
|
998
|
+
| GET | `/v5/users/exercises/history` | All exercise history |
|
|
999
|
+
| GET | `/v5/users/exercises/recent` | Recent exercises |
|
|
1000
|
+
| GET | `/v5/exercises/{id}/history` | Single exercise history |
|
|
1001
|
+
| GET | `/v5/exercises/{id}` | Exercise details |
|
|
1002
|
+
| GET | `/v5/exercises/{id}/personalRecords` | Exercise PRs |
|
|
1003
|
+
| GET | `/v5/exercises/{id}/stats` | Exercise stats |
|
|
1004
|
+
| GET | `/v5/exercises/{id}/stackUp/isSupportedExercise` | Stack-up support check |
|
|
1005
|
+
| GET | `/v5/users/circuits/recent` | Recent circuits |
|
|
1006
|
+
| GET | `/v5/users/circuits/history` | Circuit history |
|
|
1007
|
+
| GET | `/3.0/athlete/programworkout/range?startDate=&endDate=` | Workouts by date range |
|
|
1008
|
+
| GET | `/1.0/athlete/programming/programs` | Athlete programs |
|
|
1009
|
+
| GET | `/v5/users/{id}` | User profile |
|
|
1010
|
+
| GET | `/1.0/athlete/prefs` | Athlete preferences |
|
|
1011
|
+
| GET | `/2.0/athlete/workingMax` | Working maxes (all exercises with WM tracking) |
|
|
1012
|
+
| GET | `/1.0/athlete/savedworkoutset/{id}` | Saved workout set |
|
|
1013
|
+
| GET | `/1.0/athlete/savedworkout/{id}` | Saved workout |
|
|
1014
|
+
| GET | `/1.0/user/userInfo` | User info |
|
|
1015
|
+
| GET | `/v5/calendars/athletes/{id}/coachAthleteTeam` | Coach-athlete team calendar |
|
|
1016
|
+
| GET | `/v5/users/{id}/workingMaxes/{id1}` | Specific working max |
|
|
1017
|
+
| GET | `/v5/programs/new` | New programs |
|
|
1018
|
+
| GET | `/v5/programs/free` | Free programs |
|
|
1019
|
+
| GET | `/v5/athleteProfile/summary` | Athlete profile summary |
|
|
1020
|
+
| GET | `/3.0/athlete/leaderboard/{id}` | Leaderboard |
|
|
1021
|
+
|
|
1022
|
+
---
|
|
1023
|
+
|
|
1024
|
+
## Key Exercise IDs (from exercise library)
|
|
1025
|
+
|
|
1026
|
+
| ID | Title | param_1_type | param_2_type |
|
|
1027
|
+
| ---- | ----------- | ------------ | ------------ |
|
|
1028
|
+
| 1 | Back Squat | 3 (Reps) | 1 (Weight) |
|
|
1029
|
+
| 3 | Front Squat | 3 | 1 |
|
|
1030
|
+
| 7 | Pull-Up | 3 | 0 |
|
|
1031
|
+
| 24 | Burpee | 3 | 0 |
|
|
1032
|
+
| 36 | Air Squat | 3 | 0 |
|
|
1033
|
+
| 67 | Plank | 4 (Time) | 0 |
|
|
1034
|
+
| 100 | Push-Up | 3 | 0 |
|
|
1035
|
+
| 424 | Deadlift | 3 | 1 |
|
|
1036
|
+
| 1162 | Bench Press | 3 | 1 |
|
|
1037
|
+
|
|
1038
|
+
Full exercise library: `GET /v5/exerciseLibrary/all` (~2387 entries: 2067 exercises + 320 workout circuits)
|
|
1039
|
+
|
|
1040
|
+
---
|
|
1041
|
+
|
|
1042
|
+
## Custom Exercise Creation
|
|
1043
|
+
|
|
1044
|
+
`POST /2.0/coach/exercise/create`
|
|
1045
|
+
|
|
1046
|
+
Create exercises not in the standard library. You control the title and parameter types.
|
|
1047
|
+
|
|
1048
|
+
**Request body:**
|
|
1049
|
+
|
|
1050
|
+
```json
|
|
1051
|
+
{
|
|
1052
|
+
"title": "Goblet Cossack Squat",
|
|
1053
|
+
"param_1_type": 3,
|
|
1054
|
+
"param_2_type": 1,
|
|
1055
|
+
"instruction": "",
|
|
1056
|
+
"video_url": "",
|
|
1057
|
+
"points_of_performance": "Deep lateral squat holding KB at chest",
|
|
1058
|
+
"is_circuit": false,
|
|
1059
|
+
"type": 0,
|
|
1060
|
+
"tags": [],
|
|
1061
|
+
"swaps": [],
|
|
1062
|
+
"reference_max_exercise_id": null,
|
|
1063
|
+
"trainheroic_reference_exercise_id": null
|
|
1064
|
+
}
|
|
1065
|
+
```
|
|
1066
|
+
|
|
1067
|
+
**Response:**
|
|
1068
|
+
|
|
1069
|
+
```json
|
|
1070
|
+
{
|
|
1071
|
+
"id": 7721170,
|
|
1072
|
+
"user_id": 2771594,
|
|
1073
|
+
"title": "Goblet Cossack Squat",
|
|
1074
|
+
"param_1_type": 3,
|
|
1075
|
+
"param_2_type": 1,
|
|
1076
|
+
"can_edit": 1,
|
|
1077
|
+
"use_count": 0
|
|
1078
|
+
}
|
|
1079
|
+
```
|
|
1080
|
+
|
|
1081
|
+
Custom exercises can use any param type combo (see parameter types table). Examples:
|
|
1082
|
+
|
|
1083
|
+
- `p1=5, p2=0` → Distance only (e.g. "Walking Lunge for Distance" → shows as "3 x 50yd")
|
|
1084
|
+
- `p1=5, p2=1` → Distance @ Weight (e.g. "Weighted Walking Lunge for Distance" → "2 x 40yd @ 50lb")
|
|
1085
|
+
- `p1=3, p2=0` → Reps only / bodyweight (e.g. Push-Up → "3 x 15")
|
|
1086
|
+
- `p1=3, p2=1` → Reps @ Weight (e.g. "Goblet Cossack Squat" → "3 x 8 @ 35lb")
|
|
1087
|
+
|
|
1088
|
+
---
|
|
1089
|
+
|
|
1090
|
+
## Advanced Prescription Patterns
|
|
1091
|
+
|
|
1092
|
+
### Pyramid Sets
|
|
1093
|
+
|
|
1094
|
+
Use varying values across `param_1_data_N` and `param_2_data_N` to create pyramids (ascending then descending):
|
|
1095
|
+
|
|
1096
|
+
```json
|
|
1097
|
+
{
|
|
1098
|
+
"exercise_id": 1,
|
|
1099
|
+
"title": "Back Squat",
|
|
1100
|
+
"instruction": "Pyramid: work up to heavy single then back down",
|
|
1101
|
+
"param_1_type": 3,
|
|
1102
|
+
"param_2_type": 1,
|
|
1103
|
+
"param_1_data_1": "5",
|
|
1104
|
+
"param_2_data_1": "185",
|
|
1105
|
+
"param_1_data_2": "3",
|
|
1106
|
+
"param_2_data_2": "225",
|
|
1107
|
+
"param_1_data_3": "1",
|
|
1108
|
+
"param_2_data_3": "275",
|
|
1109
|
+
"param_1_data_4": "3",
|
|
1110
|
+
"param_2_data_4": "225",
|
|
1111
|
+
"param_1_data_5": "5",
|
|
1112
|
+
"param_2_data_5": "185",
|
|
1113
|
+
"param_count": 5
|
|
1114
|
+
}
|
|
1115
|
+
```
|
|
1116
|
+
|
|
1117
|
+
Displays as: `5, 3, 1, 3, 5 @ 185, 225, 275, 225, 185lb`
|
|
1118
|
+
|
|
1119
|
+
### Bodyweight Exercises (No Weight)
|
|
1120
|
+
|
|
1121
|
+
Set `param_2_type: 0` and leave all `param_2_data_N` empty:
|
|
1122
|
+
|
|
1123
|
+
```json
|
|
1124
|
+
{
|
|
1125
|
+
"exercise_id": 100,
|
|
1126
|
+
"title": "Push-Up",
|
|
1127
|
+
"param_1_type": 3,
|
|
1128
|
+
"param_2_type": 0,
|
|
1129
|
+
"param_1_data_1": "20",
|
|
1130
|
+
"param_1_data_2": "15",
|
|
1131
|
+
"param_1_data_3": "10",
|
|
1132
|
+
"param_2_data_1": "",
|
|
1133
|
+
"param_2_data_2": "",
|
|
1134
|
+
"param_2_data_3": "",
|
|
1135
|
+
"param_count": 3
|
|
1136
|
+
}
|
|
1137
|
+
```
|
|
1138
|
+
|
|
1139
|
+
Displays as: `20, 15, 10` (reps only)
|
|
1140
|
+
|
|
1141
|
+
Many existing exercises default to bodyweight (p2=0 or p2=None):
|
|
1142
|
+
|
|
1143
|
+
- Push-Up (100), Pull-Up (7), Air Squat (36), Burpee (24)
|
|
1144
|
+
- ~970+ exercises in the library are reps-only (p2=0 or p2=None)
|
|
1145
|
+
|
|
1146
|
+
### Distance-Based Exercises
|
|
1147
|
+
|
|
1148
|
+
The distance unit is the exercise's fixed default (see the fixed-unit note under
|
|
1149
|
+
"Parameter types") — you only get the unit the exercise already carries. The
|
|
1150
|
+
examples below work because the `exercise_id`s are **custom** exercises created
|
|
1151
|
+
with that `param_1_type`; sending `param_1_type: 5` to a stock reps or miles
|
|
1152
|
+
exercise is ignored. To get a unit a stock exercise lacks, create a custom
|
|
1153
|
+
exercise with the unit you want, or pick a stock exercise whose default matches
|
|
1154
|
+
(`Sprint` 127 = meters, a `*yd` carry = yards, `Run` 82 = miles).
|
|
1155
|
+
|
|
1156
|
+
For a custom exercise created with `param_1_type: 5`, distance displays as yards:
|
|
1157
|
+
|
|
1158
|
+
```json
|
|
1159
|
+
{
|
|
1160
|
+
"exercise_id": 7721172,
|
|
1161
|
+
"title": "Walking Lunge for Distance",
|
|
1162
|
+
"param_1_type": 5,
|
|
1163
|
+
"param_2_type": 0,
|
|
1164
|
+
"param_1_data_1": "50",
|
|
1165
|
+
"param_1_data_2": "50",
|
|
1166
|
+
"param_1_data_3": "50",
|
|
1167
|
+
"param_count": 3
|
|
1168
|
+
}
|
|
1169
|
+
```
|
|
1170
|
+
|
|
1171
|
+
Displays as: `3 x 50yd`
|
|
1172
|
+
|
|
1173
|
+
With weight (`param_2_type: 1`):
|
|
1174
|
+
|
|
1175
|
+
```json
|
|
1176
|
+
{
|
|
1177
|
+
"exercise_id": 7721174,
|
|
1178
|
+
"title": "Weighted Walking Lunge for Distance",
|
|
1179
|
+
"param_1_type": 5,
|
|
1180
|
+
"param_2_type": 1,
|
|
1181
|
+
"param_1_data_1": "40",
|
|
1182
|
+
"param_1_data_2": "40",
|
|
1183
|
+
"param_2_data_1": "50",
|
|
1184
|
+
"param_2_data_2": "50",
|
|
1185
|
+
"param_count": 2
|
|
1186
|
+
}
|
|
1187
|
+
```
|
|
1188
|
+
|
|
1189
|
+
Displays as: `2 x 40yd @ 50lb`
|
|
1190
|
+
|
|
1191
|
+
**Distance type values:**
|
|
1192
|
+
| param type | Unit |
|
|
1193
|
+
|-----------|------|
|
|
1194
|
+
| 5 | yards |
|
|
1195
|
+
| 6 | meters |
|
|
1196
|
+
| 10 | miles |
|
|
1197
|
+
| 11 | feet |
|
|
1198
|
+
|
|
1199
|
+
Existing distance exercises: 193 in library (e.g. Sled Push, Bear Crawl, Sprint, Carioca, Run)
|
|
1200
|
+
|
|
1201
|
+
### Lunge Exercises in Library
|
|
1202
|
+
|
|
1203
|
+
102 lunge exercises exist. Most use Reps (p1=3):
|
|
1204
|
+
|
|
1205
|
+
- Walking Lunges (77): `p1=6 (meters), p2=None` — already distance-based!
|
|
1206
|
+
- DB Walking Lunge (5947818): `p1=3, p2=1`
|
|
1207
|
+
- Body Weight Lunge (5947644): `p1=3, p2=None`
|
|
1208
|
+
- Tandem Resisted Lunge Walks (688456): `p1=5 (yards), p2=None` — distance-based
|
|
1209
|
+
|
|
1210
|
+
To make any lunge distance-based, create a custom exercise with `param_1_type: 5`.
|
|
1211
|
+
|
|
1212
|
+
---
|
|
1213
|
+
|
|
1214
|
+
## Required Fields for saveWorkoutSetExercises
|
|
1215
|
+
|
|
1216
|
+
The API requires these additional fields beyond the basic ones (discovered via UI capture):
|
|
1217
|
+
|
|
1218
|
+
```json
|
|
1219
|
+
{
|
|
1220
|
+
"exercise_id": 1,
|
|
1221
|
+
"workout_set_id": 671135671,
|
|
1222
|
+
"set_id": 671135671,
|
|
1223
|
+
"title": "Back Squat",
|
|
1224
|
+
"instruction": "",
|
|
1225
|
+
"order": 1,
|
|
1226
|
+
"param_1_type": 3,
|
|
1227
|
+
"param_2_type": 1,
|
|
1228
|
+
"param_1_data_1": "5", "param_1_data_2": "", ..., "param_1_data_10": "",
|
|
1229
|
+
"param_2_data_1": "225", "param_2_data_2": "", ..., "param_2_data_10": "",
|
|
1230
|
+
"workout_set_exercise_template_id": null,
|
|
1231
|
+
"no_sets": 0,
|
|
1232
|
+
"param_count": 3,
|
|
1233
|
+
"set_num": 3,
|
|
1234
|
+
"key": "k::5001",
|
|
1235
|
+
"setKey": 671135671,
|
|
1236
|
+
"video_url": "",
|
|
1237
|
+
"thumbnail_url": "",
|
|
1238
|
+
"tags": [],
|
|
1239
|
+
"eType": "e",
|
|
1240
|
+
"use_count": 0
|
|
1241
|
+
}
|
|
1242
|
+
```
|
|
1243
|
+
|
|
1244
|
+
**Critical fields that cause 500 errors if missing:**
|
|
1245
|
+
|
|
1246
|
+
- `set_num` — number of sets (same as `param_count`)
|
|
1247
|
+
- `key` — unique key string (format: `"k::<number>"`, can be any unique value)
|
|
1248
|
+
- `setKey` — same as `workout_set_id`
|
|
1249
|
+
- All 10 `param_1_data_N` and `param_2_data_N` fields (empty string `""` for unused slots)
|
|
1250
|
+
- `eType` — `"e"` for exercise
|
|
1251
|
+
- `tags` — array (can be empty `[]`)
|
|
1252
|
+
- `video_url`, `thumbnail_url` — strings (can be empty `""`)
|
|
1253
|
+
- `use_count` — integer (can be `0`)
|
|
1254
|
+
- `workout_set_exercise_template_id` — null
|
|
1255
|
+
|
|
1256
|
+
---
|
|
1257
|
+
|
|
1258
|
+
## Session Management
|
|
1259
|
+
|
|
1260
|
+
| Method | Endpoint | Description |
|
|
1261
|
+
| ------ | ---------------------------------------------------------------------- | --------------------------- |
|
|
1262
|
+
| POST | `/2.0/coach/calendar/programWorkout/unPublish/{programWorkoutId}` | **Unpublish session** |
|
|
1263
|
+
| POST | `/2.0/coach/calendar/removeProgramWorkout` | **Delete session** |
|
|
1264
|
+
| POST | `/2.0/coach/calendar/copyProgramWorkout` | **Copy/Repeat session** |
|
|
1265
|
+
| POST | `/2.0/coach/calendar/programWorkout/saveWorkoutAsTemplate/{workoutId}` | **Save session to library** |
|
|
1266
|
+
|
|
1267
|
+
#### `POST /2.0/coach/calendar/programWorkout/unPublish/{programWorkoutId}`
|
|
1268
|
+
|
|
1269
|
+
Unpublishes a previously published session. No request body needed.
|
|
1270
|
+
|
|
1271
|
+
#### `POST /2.0/coach/calendar/removeProgramWorkout`
|
|
1272
|
+
|
|
1273
|
+
Deletes a session from the calendar.
|
|
1274
|
+
|
|
1275
|
+
**Request body:**
|
|
1276
|
+
|
|
1277
|
+
```json
|
|
1278
|
+
{
|
|
1279
|
+
"programId": 4713246,
|
|
1280
|
+
"pwId": 142002657
|
|
1281
|
+
}
|
|
1282
|
+
```
|
|
1283
|
+
|
|
1284
|
+
#### `POST /2.0/coach/calendar/copyProgramWorkout`
|
|
1285
|
+
|
|
1286
|
+
Copies or repeats a session to a target date. Used for both "Copy" and "Repeat" context menu actions.
|
|
1287
|
+
|
|
1288
|
+
**Request body:**
|
|
1289
|
+
|
|
1290
|
+
```json
|
|
1291
|
+
{
|
|
1292
|
+
"toProgramId": 4713246,
|
|
1293
|
+
"pwId": 142002657,
|
|
1294
|
+
"toDate": {
|
|
1295
|
+
"date": "2026-03-15",
|
|
1296
|
+
"day": 15,
|
|
1297
|
+
"month": 3,
|
|
1298
|
+
"year": 2026,
|
|
1299
|
+
"dayOfWeek": 0,
|
|
1300
|
+
"isToday": false
|
|
1301
|
+
}
|
|
1302
|
+
}
|
|
1303
|
+
```
|
|
1304
|
+
|
|
1305
|
+
#### `POST /2.0/coach/calendar/programWorkout/saveWorkoutAsTemplate/{workoutId}`
|
|
1306
|
+
|
|
1307
|
+
Saves an existing session as a reusable template in the session library. No request body needed — uses the workout ID in the URL path.
|
|
1308
|
+
|
|
1309
|
+
---
|
|
1310
|
+
|
|
1311
|
+
## Team Management
|
|
1312
|
+
|
|
1313
|
+
| Method | Endpoint | Description |
|
|
1314
|
+
| ------ | --------------------------------------- | -------------------------------------- |
|
|
1315
|
+
| PUT | `/v5/teams/{teamId}` | **Update team settings** (title, etc.) |
|
|
1316
|
+
| DELETE | `/v5/teams/{teamId}` | **Delete team** |
|
|
1317
|
+
| POST | `/1.0/coach/team/updatePublishSettings` | **Update auto-publish settings** |
|
|
1318
|
+
|
|
1319
|
+
#### `PUT /v5/teams/{teamId}`
|
|
1320
|
+
|
|
1321
|
+
Updates team properties like title.
|
|
1322
|
+
|
|
1323
|
+
**Request body:**
|
|
1324
|
+
|
|
1325
|
+
```json
|
|
1326
|
+
{
|
|
1327
|
+
"title": "New Team Name"
|
|
1328
|
+
}
|
|
1329
|
+
```
|
|
1330
|
+
|
|
1331
|
+
#### `POST /1.0/coach/team/updatePublishSettings`
|
|
1332
|
+
|
|
1333
|
+
Updates the auto-publish settings for a team's program. Takes the full program object with `pub_*` fields controlling auto-publish behavior.
|
|
1334
|
+
|
|
1335
|
+
---
|
|
1336
|
+
|
|
1337
|
+
## Coach Dashboard
|
|
1338
|
+
|
|
1339
|
+
| Method | Endpoint | Description |
|
|
1340
|
+
| ------ | --------------------------------------------------------------- | --------------------------------- |
|
|
1341
|
+
| GET | `/v5/coaches/activityFeed?page={n}&pageSize={n}` | Coach activity feed |
|
|
1342
|
+
| GET | `/v5/coaches/lowProgramming` | Low programming alerts |
|
|
1343
|
+
| GET | `/v5/coaches/onboarding` | Onboarding tracking |
|
|
1344
|
+
| GET | `/v5/coaches/athletes/{athleteId}/workouts?startDate=&endDate=` | Athlete workout data with surveys |
|
|
1345
|
+
|
|
1346
|
+
---
|
|
1347
|
+
|
|
1348
|
+
## Still Unexplored
|
|
1349
|
+
|
|
1350
|
+
- Program update (title, description, settings) — PUT/POST patterns return 405/404
|
|
1351
|
+
- Athlete remove from specific team (endpoint pattern not found via probing)
|
|
1352
|
+
- Working max set/update from coach side for specific athletes
|
|
1353
|
+
- Marketplace endpoints (publishing, pricing, purchases)
|
|
1354
|
+
- Notification management (mark read, dismiss — 401/404 on tested patterns)
|
|
1355
|
+
- `apis.trainheroic.com/user` endpoint (uses `api-token` header, returns full user profile with teams)
|
|
1356
|
+
- Circuit creation (vs superset — circuits may use different block type or field)
|
|
1357
|
+
- Prescription template CRUD
|
|
1358
|
+
- Coach preferences update (PATCH/PUT on `2.0/coach/prefs` returns 403/405)
|
|
1359
|
+
- Library settings
|
|
1360
|
+
- Session template update (edit existing template)
|
|
1361
|
+
- Workout set reorder / move exercises between blocks
|