Update a training plan
PATCH/api/public/training-plans/{planId}
Partially update a training plan. Pass only fields you want to change.
BREAKING CHANGE the plan name is now "title"; the previous "name" field has been removed. Unknown fields are dropped during validation, so a PATCH sending only "name" is rejected with 400 "No updatable fields were supplied" rather than silently doing nothing.
If the plan is attached to more than one trainee, editing it changes the plan for all of them, so the request returns 409 CONFLICT until you confirm with applyToAllSharedTrainees.
Authentication
Requires an API key, sent as Authorization: Bearer cp_live_....
API keys carry scopes. Get the catalog of scopes and webhook events lists every scope.
Path parameters
| Name | Type | Description |
|---|---|---|
planIdrequired | string |
Header parameters
| Name | Type | Description |
|---|---|---|
Idempotency-Key | string | Optional. Send a unique key (e.g. a UUID) to make this POST safe to retry. The same key within 24h returns the original result instead of creating a duplicate. |
Request body
application/json, required
| Name | Type | Description |
|---|---|---|
title | string | |
description | string | |
level | string | |
maxDuration | number | |
workouts | object[] | |
workouts[].trainingName | string | Display name of the day, e.g. "Day A — Push". |
workouts[].trainingType | string | Day label, e.g. "A", "B", "FullBody". |
workouts[].exerciseOrder | string | How exercises are performed. Defaults to Sequential. |
workouts[].notes | string | Free-text note for the whole day. |
workouts[].timeBasedDetails | object | Timing configuration for circuit-style days (Tabata, HIIT, EMOM, AMRAP). Seconds unless noted. |
workouts[].timeBasedDetails.totalRounds | number | |
workouts[].timeBasedDetails.timeLimit | number | |
workouts[].timeBasedDetails.workInterval | number | |
workouts[].timeBasedDetails.restInterval | number | |
workouts[].timeBasedDetails.restBetweenRounds | number | |
workouts[].exercises | object[] | |
workouts[].exercises[].exerciseDetails | string | Exercise catalog id, taken from |
workouts[].exercises[].setsNumber | string | Number of sets, e.g. "3". |
workouts[].exercises[].repsNumber | string | Reps per set, e.g. "10" or "8-12". |
workouts[].exercises[].restTime | string | Rest between sets in seconds, e.g. "90". |
workouts[].exercises[].isDurationBased | boolean | True for timed exercises (e.g. plank) instead of reps. |
workouts[].exercises[].setDuration | string | Duration per set in seconds when isDurationBased is true. |
workouts[].exercises[].weightPercentage | number | Working weight as % of 1RM, e.g. 75. |
workouts[].exercises[].customNotes | string | Free-text note shown to the trainee for this exercise. |
workouts[].exercises[].weight | number | Working weight in kg for the exercise. |
workouts[].exercises[].sets | object[] | Per-set prescription. Takes precedence over setsNumber/repsNumber when present. |
workouts[].exercises[].sets[].setNumber | number | 1-based position of the set. |
workouts[].exercises[].sets[].reps | string | Reps for this set, e.g. "8". |
workouts[].exercises[].sets[].weight | number | Working weight for this set. |
workouts[].exercises[].sets[].restTime | string | Rest after this set in seconds. |
workouts[].exercises[].sets[].intensityValue | number | Intensity for this set, read against intensityType. |
workouts[].exercises[].sets[].dropSet | string | |
workouts[].exercises[].sets[].isWarmupSet | boolean | Warmup sets are not counted towards working volume. |
workouts[].exercises[].superSet | boolean | True when this exercise belongs to a superset. |
workouts[].exercises[].superSetGroup | string | Shared identifier grouping the exercises performed together in one superset. |
workouts[].exercises[].dropSet | string | |
workouts[].exercises[].restPause | boolean | Rest-pause technique. |
workouts[].exercises[].cluster | boolean | Cluster-set technique. |
workouts[].exercises[].trackingType | string | What the trainee logs for this exercise. |
workouts[].exercises[].intensityType | string | How intensityValue is interpreted. |
workouts[].exercises[].intensityValue | number | Intensity target. |
workouts[].exercises[].tempo | object | Tempo in seconds per phase of the lift. |
workouts[].exercises[].tempo.eccentric | number | |
workouts[].exercises[].tempo.hold | number | |
workouts[].exercises[].tempo.concentric | number | |
workouts[].exercises[].tempo.rest | number | |
workouts[].exercises[].distance | number | Distance for cardio exercises. |
workouts[].exercises[].distanceUnit | string | |
workouts[].exercises[].specificAlternativeExercises | string[] | Catalog ids the trainee may swap in for this exercise. |
applyToAllSharedTrainees | boolean | Required (true) to edit a plan shared by multiple trainees; the change then applies to all of them. Without it, a shared plan returns 409. |
Request examples
curl --request PATCH \
--url 'https://api.coach-platform.com/api/public/training-plans/<planId>' \
--header 'Authorization: Bearer cp_live_...' \
--header 'Content-Type: application/json' \
--data '{
"title": "<string>"
}'const response = await fetch('https://api.coach-platform.com/api/public/training-plans/<planId>', {
method: 'PATCH',
headers: {
Authorization: 'Bearer cp_live_...',
'Content-Type': 'application/json',
},
body: JSON.stringify({
"title": "<string>"
}),
});
const data = await response.json();Responses
200 OK
Successful response
{
"data": {
"id": "<string>",
"coach": "<string>",
"escorts": [
"<string>"
],
"title": "<string>",
"notes": "<string>",
"level": "Beginner",
"maxDuration": 123,
"isTemplate": true,
"workouts": [
{
"trainingName": "<string>",
"trainingType": "A",
"exerciseOrder": "Sequential",
"notes": "<string>",
"timeBasedDetails": {
"totalRounds": 123,
"timeLimit": 123,
"workInterval": 123,
"restInterval": 123,
"restBetweenRounds": 123
},
"exercises": [
{
"exerciseDetails": {
"id": "<string>",
"name": "<string>"
},
"setsNumber": "<string>",
"repsNumber": "<string>",
"restTime": "<string>",
"isDurationBased": true,
"setDuration": "<string>",
"weightPercentage": 123,
"customNotes": "<string>",
"weight": 123,
"sets": [
{
"setNumber": 123,
"reps": "<string>",
"weight": 123,
"restTime": "<string>",
"intensityValue": 123,
"dropSet": "DropSet",
"isWarmupSet": true
}
],
"superSet": true,
"superSetGroup": "<string>",
"dropSet": "DropSet",
"restPause": true,
"cluster": true,
"trackingType": "weight_reps",
"intensityType": "Percentage",
"intensityValue": 123,
"tempo": {
"eccentric": 123,
"hold": 123,
"concentric": 123,
"rest": 123
},
"distance": 123,
"distanceUnit": "meters",
"specificAlternativeExercises": [
"<string>"
]
}
]
}
],
"createdAt": "<date-time>"
}
}Response fields (57)
| Name | Type | Description |
|---|---|---|
data | object | |
data.id | string | |
data.coach | string | |
data.escorts | string[] | |
data.title | string | |
data.notes | string | |
data.level | string | |
data.maxDuration | number | Target session length in minutes. |
data.isTemplate | boolean | True when the plan is a reusable template. |
data.workouts | object[] | |
data.workouts[].trainingName | string | Display name of the day, e.g. "Day A — Push". |
data.workouts[].trainingType | string | Day label, e.g. "A", "B", "FullBody". |
data.workouts[].exerciseOrder | string | How exercises are performed. Defaults to Sequential. |
data.workouts[].notes | string | Free-text note for the whole day. |
data.workouts[].timeBasedDetails | object | Timing configuration for circuit-style days (Tabata, HIIT, EMOM, AMRAP). Seconds unless noted. |
data.workouts[].timeBasedDetails.totalRounds | number | |
data.workouts[].timeBasedDetails.timeLimit | number | |
data.workouts[].timeBasedDetails.workInterval | number | |
data.workouts[].timeBasedDetails.restInterval | number | |
data.workouts[].timeBasedDetails.restBetweenRounds | number | |
data.workouts[].exercises | object[] | |
data.workouts[].exercises[].exerciseDetails | object | The exercise catalog entry. On write, pass exerciseDetails as the catalog id string (from |
data.workouts[].exercises[].exerciseDetails.id | string | |
data.workouts[].exercises[].exerciseDetails.name | string | |
data.workouts[].exercises[].setsNumber | string | Number of sets, e.g. "3". |
data.workouts[].exercises[].repsNumber | string | Reps per set, e.g. "10" or "8-12". |
data.workouts[].exercises[].restTime | string | Rest between sets in seconds, e.g. "90". |
data.workouts[].exercises[].isDurationBased | boolean | True for timed exercises (e.g. plank) instead of reps. |
data.workouts[].exercises[].setDuration | string | Duration per set in seconds when isDurationBased is true. |
data.workouts[].exercises[].weightPercentage | number | Working weight as % of 1RM, e.g. 75. |
data.workouts[].exercises[].customNotes | string | Free-text note shown to the trainee for this exercise. |
data.workouts[].exercises[].weight | number | Working weight in kg for the exercise. |
data.workouts[].exercises[].sets | object[] | Per-set prescription. Takes precedence over setsNumber/repsNumber when present. |
data.workouts[].exercises[].sets[].setNumber | number | 1-based position of the set. |
data.workouts[].exercises[].sets[].reps | string | Reps for this set, e.g. "8". |
data.workouts[].exercises[].sets[].weight | number | Working weight for this set. |
data.workouts[].exercises[].sets[].restTime | string | Rest after this set in seconds. |
data.workouts[].exercises[].sets[].intensityValue | number | Intensity for this set, read against intensityType. |
data.workouts[].exercises[].sets[].dropSet | string | |
data.workouts[].exercises[].sets[].isWarmupSet | boolean | Warmup sets are not counted towards working volume. |
data.workouts[].exercises[].superSet | boolean | True when this exercise belongs to a superset. |
data.workouts[].exercises[].superSetGroup | string | Shared identifier grouping the exercises performed together in one superset. |
data.workouts[].exercises[].dropSet | string | |
data.workouts[].exercises[].restPause | boolean | Rest-pause technique. |
data.workouts[].exercises[].cluster | boolean | Cluster-set technique. |
data.workouts[].exercises[].trackingType | string | What the trainee logs for this exercise. |
data.workouts[].exercises[].intensityType | string | How intensityValue is interpreted. |
data.workouts[].exercises[].intensityValue | number | Intensity target. |
data.workouts[].exercises[].tempo | object | Tempo in seconds per phase of the lift. |
data.workouts[].exercises[].tempo.eccentric | number | |
data.workouts[].exercises[].tempo.hold | number | |
data.workouts[].exercises[].tempo.concentric | number | |
data.workouts[].exercises[].tempo.rest | number | |
data.workouts[].exercises[].distance | number | Distance for cardio exercises. |
data.workouts[].exercises[].distanceUnit | string | |
data.workouts[].exercises[].specificAlternativeExercises | string[] | Catalog ids the trainee may swap in for this exercise. |
data.createdAt | string<date-time> |
Error responses
400Bad Request401Unauthorized403Forbidden404Not Found409Conflict429Too Many Requests500Internal Server Error
These statuses share the same response body.
{
"error": {
"code": "<string>",
"message": "<string>",
"fix": "<string>",
"details": {},
"retryAfterMs": 123
}
}Error fields (6)
| Name | Type | Description |
|---|---|---|
error | object | |
error.code | string | |
error.message | string | |
error.fix | string | null | |
error.details | object | null | |
error.retryAfterMs | number | null |
More Training Plans endpoints
This page is also available as Markdown. Browse it in the interactive explorer or download the OpenAPI spec.