# Update a training plan

`PATCH https://api.coach-platform.com/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.

- Resource: Training Plans
- Authentication: API key in the `Authorization: Bearer cp_live_...` header
- HTML version: https://www.coach-platform.com/docs/api/reference/patch-training-plans-plan-id
- OpenAPI spec: https://www.coach-platform.com/openapi.json

## Path parameters

| Name | Type | Required | Description |
|---|---|---|---|
| `planId` | string | Yes |   |

## Header parameters

| Name | Type | Required | Description |
|---|---|---|---|
| `Idempotency-Key` | string | No | 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

Content type: `application/json`. Required.

| Field | Type | Required | Description |
|---|---|---|---|
| `title` | string | No |   |
| `description` | string | No |   |
| `level` | string | No | Allowed values: `Beginner`, `Intermediate`, `Advanced`. |
| `maxDuration` | number | No |   |
| `workouts` | object[] | No | Maximum items: `50`. |
| `workouts[].trainingName` | string | No | Display name of the day, e.g. "Day A — Push". |
| `workouts[].trainingType` | string | No | Day label, e.g. "A", "B", "FullBody". Allowed values: `A`, `B`, `C`, `D`, `E`, `FullBody`, `CrossFit`, `Tabata`, `HIIT`, `EMOM`, `AMRAP`, `Circuit`, `ForTime`. |
| `workouts[].exerciseOrder` | string | No | How exercises are performed. Defaults to Sequential. Allowed values: `Sequential`, `Circuit`, `Superset`, `Complex`. |
| `workouts[].notes` | string | No | Free-text note for the whole day. |
| `workouts[].timeBasedDetails` | object | No | Timing configuration for circuit-style days (Tabata, HIIT, EMOM, AMRAP). Seconds unless noted. |
| `workouts[].timeBasedDetails.totalRounds` | number | No |   |
| `workouts[].timeBasedDetails.timeLimit` | number | No |   |
| `workouts[].timeBasedDetails.workInterval` | number | No |   |
| `workouts[].timeBasedDetails.restInterval` | number | No |   |
| `workouts[].timeBasedDetails.restBetweenRounds` | number | No |   |
| `workouts[].exercises` | object[] | No |   |
| `workouts[].exercises[].exerciseDetails` | string | No | Exercise catalog id, taken from GET /exercises. |
| `workouts[].exercises[].setsNumber` | string | No | Number of sets, e.g. "3". |
| `workouts[].exercises[].repsNumber` | string | No | Reps per set, e.g. "10" or "8-12". |
| `workouts[].exercises[].restTime` | string | No | Rest between sets in seconds, e.g. "90". |
| `workouts[].exercises[].isDurationBased` | boolean | No | True for timed exercises (e.g. plank) instead of reps. |
| `workouts[].exercises[].setDuration` | string | No | Duration per set in seconds when isDurationBased is true. |
| `workouts[].exercises[].weightPercentage` | number | No | Working weight as % of 1RM, e.g. 75. |
| `workouts[].exercises[].customNotes` | string | No | Free-text note shown to the trainee for this exercise. |
| `workouts[].exercises[].weight` | number | No | Working weight in kg for the exercise. |
| `workouts[].exercises[].sets` | object[] | No | Per-set prescription. Takes precedence over setsNumber/repsNumber when present. |
| `workouts[].exercises[].sets[].setNumber` | number | No | 1-based position of the set. |
| `workouts[].exercises[].sets[].reps` | string | No | Reps for this set, e.g. "8". |
| `workouts[].exercises[].sets[].weight` | number | No | Working weight for this set. |
| `workouts[].exercises[].sets[].restTime` | string | No | Rest after this set in seconds. |
| `workouts[].exercises[].sets[].intensityValue` | number | No | Intensity for this set, read against intensityType. |
| `workouts[].exercises[].sets[].dropSet` | string | No | Allowed values: `DropSet`, `DoubleDropSet`, `TripleDropSet`. |
| `workouts[].exercises[].sets[].isWarmupSet` | boolean | No | Warmup sets are not counted towards working volume. |
| `workouts[].exercises[].superSet` | boolean | No | True when this exercise belongs to a superset. |
| `workouts[].exercises[].superSetGroup` | string | No | Shared identifier grouping the exercises performed together in one superset. |
| `workouts[].exercises[].dropSet` | string | No | Allowed values: `DropSet`, `DoubleDropSet`, `TripleDropSet`. |
| `workouts[].exercises[].restPause` | boolean | No | Rest-pause technique. |
| `workouts[].exercises[].cluster` | boolean | No | Cluster-set technique. |
| `workouts[].exercises[].trackingType` | string | No | What the trainee logs for this exercise. Allowed values: `weight_reps`, `reps_only`, `duration`, `completion`. |
| `workouts[].exercises[].intensityType` | string | No | How intensityValue is interpreted. Allowed values: `Percentage`, `RPE`, `RIR`. |
| `workouts[].exercises[].intensityValue` | number | No | Intensity target. |
| `workouts[].exercises[].tempo` | object | No | Tempo in seconds per phase of the lift. |
| `workouts[].exercises[].tempo.eccentric` | number | No |   |
| `workouts[].exercises[].tempo.hold` | number | No |   |
| `workouts[].exercises[].tempo.concentric` | number | No |   |
| `workouts[].exercises[].tempo.rest` | number | No |   |
| `workouts[].exercises[].distance` | number | No | Distance for cardio exercises. |
| `workouts[].exercises[].distanceUnit` | string | No | Allowed values: `meters`, `km`, `miles`, `yards`. |
| `workouts[].exercises[].specificAlternativeExercises` | string[] | No | Catalog ids the trainee may swap in for this exercise. |
| `applyToAllSharedTrainees` | boolean | No | 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. |

## Responses

| Status | Description |
|---|---|
| `200` OK | Successful response |
| `400` Bad Request |  |
| `401` Unauthorized |  |
| `403` Forbidden |  |
| `404` Not Found |  |
| `409` Conflict |  |
| `429` Too Many Requests |  |
| `500` Internal Server Error |  |

### 200 OK

Content type: `application/json`

| Field | Type | Description |
|---|---|---|
| `data` | object |   |
| `data.id` | string |   |
| `data.coach` | string |   |
| `data.escorts` | string[] |   |
| `data.title` | string |   |
| `data.notes` | string |   |
| `data.level` | string | Allowed values: `Beginner`, `Intermediate`, `Advanced`. |
| `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". Allowed values: `A`, `B`, `C`, `D`, `E`, `FullBody`, `CrossFit`, `Tabata`, `HIIT`, `EMOM`, `AMRAP`, `Circuit`, `ForTime`. |
| `data.workouts[].exerciseOrder` | string | How exercises are performed. Defaults to Sequential. Allowed values: `Sequential`, `Circuit`, `Superset`, `Complex`. |
| `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 GET /exercises). |
| `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 | Allowed values: `DropSet`, `DoubleDropSet`, `TripleDropSet`. |
| `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 | Allowed values: `DropSet`, `DoubleDropSet`, `TripleDropSet`. |
| `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. Allowed values: `weight_reps`, `reps_only`, `duration`, `completion`. |
| `data.workouts[].exercises[].intensityType` | string | How intensityValue is interpreted. Allowed values: `Percentage`, `RPE`, `RIR`. |
| `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 | Allowed values: `meters`, `km`, `miles`, `yards`. |
| `data.workouts[].exercises[].specificAlternativeExercises` | string[] | Catalog ids the trainee may swap in for this exercise. |
| `data.createdAt` | string<date-time> |   |

Example:

```json
{
  "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>"
  }
}
```

### Error responses 400, 401, 403, 404, 409, 429, 500

Content type: `application/json`

| Field | Type | Description |
|---|---|---|
| `error` | object |   |
| `error.code` | string |   |
| `error.message` | string |   |
| `error.fix` | string \| null |   |
| `error.details` | object \| null |   |
| `error.retryAfterMs` | number \| null |   |

Example:

```json
{
  "error": {
    "code": "<string>",
    "message": "<string>",
    "fix": "<string>",
    "details": {},
    "retryAfterMs": 123
  }
}
```

## Examples

### cURL

```bash
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>"
}'
```

### JavaScript (fetch)

```js
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();
```

## Related endpoints

- [List training plans](https://www.coach-platform.com/docs/api/reference/get-training-plans/index.md): `GET /api/public/training-plans`. List training plans.
- [Create a training plan](https://www.coach-platform.com/docs/api/reference/post-training-plans/index.md): `POST /api/public/training-plans`. Create a training plan.
- [Get a training plan](https://www.coach-platform.com/docs/api/reference/get-training-plans-plan-id/index.md): `GET /api/public/training-plans/{planId}`. Get a training plan by id.
- [Delete a training plan](https://www.coach-platform.com/docs/api/reference/delete-training-plans-plan-id/index.md): `DELETE /api/public/training-plans/{planId}`. Delete a training plan permanently.
- [Get a trainee's active training plan](https://www.coach-platform.com/docs/api/reference/get-trainees-trainee-id-active-training-plan/index.md): `GET /api/public/trainees/{traineeId}/active-training-plan`. Get a trainee's currently active training plan (resolved via the active coaching period).
- [Duplicate a training plan](https://www.coach-platform.com/docs/api/reference/post-training-plans-plan-id-duplicate/index.md): `POST /api/public/training-plans/{planId}/duplicate`. Copy an existing training plan, including every exercise detail (per-set prescriptions, supersets, tempo, intensity, drop sets, alternatives).
- Previous: [Get a training plan](https://www.coach-platform.com/docs/api/reference/get-training-plans-plan-id/index.md): `GET /api/public/training-plans/{planId}`. Get a training plan by id.
- Next: [Delete a training plan](https://www.coach-platform.com/docs/api/reference/delete-training-plans-plan-id/index.md): `DELETE /api/public/training-plans/{planId}`. Delete a training plan permanently.
