Create a training plan

POST/api/public/training-plans

Create a training plan. Provide traineeId (or escortId) to attach it to a coaching period, or omit both to leave it unassigned. Set isTemplate to save it as a reusable template. The workouts array holds an ordered list of workout days; each day has a trainingName and an exercises array. Each exercise references a catalog id via exerciseDetails (get ids from GET /exercises).

BREAKING CHANGE the plan name is now "title". The previous "name" field has been removed, so a request sending "name" is rejected with 400 "must have required property title". This matches the field name used in every read response. The same rename applies to PATCH /training-plans/{planId}.

To build a plan from an existing one, do not read it and re-create it: that silently drops per-set prescriptions, supersets, tempo and intensity. Use POST /training-plans/{planId}/duplicate instead.

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.

Header parameters

Header parameters
NameTypeDescription
Idempotency-Keystring

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

Request body fields
NameTypeDescription
traineeIdstring

Owning trainee id. Omit both traineeId and escortId to leave the plan unassigned.

isTemplateboolean

Save as a reusable template. A template cannot be assigned, so omit traineeId and escortId.

escortIdstring

Optional coaching period id

titlerequiredstring

Plan title shown to the trainee

descriptionstring
levelstring

Difficulty level of the plan.

Allowed values: Beginner Intermediate Advanced

maxDurationnumber

Target session length in minutes.

workoutsobject[]

Ordered workout days (e.g. A/B/C splits)

Maximum items: 50

workouts[].trainingNamestring

Display name of the day, e.g. "Day A — Push".

workouts[].trainingTypestring

Day label, e.g. "A", "B", "FullBody".

Allowed values: A B C D E FullBody CrossFit Tabata HIIT EMOM AMRAP Circuit ForTime

workouts[].exerciseOrderstring

How exercises are performed. Defaults to Sequential.

Allowed values: Sequential Circuit Superset Complex

workouts[].notesstring

Free-text note for the whole day.

workouts[].timeBasedDetailsobject

Timing configuration for circuit-style days (Tabata, HIIT, EMOM, AMRAP). Seconds unless noted.

workouts[].timeBasedDetails.totalRoundsnumber
workouts[].timeBasedDetails.timeLimitnumber
workouts[].timeBasedDetails.workIntervalnumber
workouts[].timeBasedDetails.restIntervalnumber
workouts[].timeBasedDetails.restBetweenRoundsnumber
workouts[].exercisesobject[]
workouts[].exercises[].exerciseDetailsstring

Exercise catalog id, taken from GET /exercises.

workouts[].exercises[].setsNumberstring

Number of sets, e.g. "3".

workouts[].exercises[].repsNumberstring

Reps per set, e.g. "10" or "8-12".

workouts[].exercises[].restTimestring

Rest between sets in seconds, e.g. "90".

workouts[].exercises[].isDurationBasedboolean

True for timed exercises (e.g. plank) instead of reps.

workouts[].exercises[].setDurationstring

Duration per set in seconds when isDurationBased is true.

workouts[].exercises[].weightPercentagenumber

Working weight as % of 1RM, e.g. 75.

workouts[].exercises[].customNotesstring

Free-text note shown to the trainee for this exercise.

workouts[].exercises[].weightnumber

Working weight in kg for the exercise.

workouts[].exercises[].setsobject[]

Per-set prescription. Takes precedence over setsNumber/repsNumber when present.

workouts[].exercises[].sets[].setNumbernumber

1-based position of the set.

workouts[].exercises[].sets[].repsstring

Reps for this set, e.g. "8".

workouts[].exercises[].sets[].weightnumber

Working weight for this set.

workouts[].exercises[].sets[].restTimestring

Rest after this set in seconds.

workouts[].exercises[].sets[].intensityValuenumber

Intensity for this set, read against intensityType.

workouts[].exercises[].sets[].dropSetstring

Allowed values: DropSet DoubleDropSet TripleDropSet

workouts[].exercises[].sets[].isWarmupSetboolean

Warmup sets are not counted towards working volume.

workouts[].exercises[].superSetboolean

True when this exercise belongs to a superset.

workouts[].exercises[].superSetGroupstring

Shared identifier grouping the exercises performed together in one superset.

workouts[].exercises[].dropSetstring

Allowed values: DropSet DoubleDropSet TripleDropSet

workouts[].exercises[].restPauseboolean

Rest-pause technique.

workouts[].exercises[].clusterboolean

Cluster-set technique.

workouts[].exercises[].trackingTypestring

What the trainee logs for this exercise.

Allowed values: weight_reps reps_only duration completion

workouts[].exercises[].intensityTypestring

How intensityValue is interpreted.

Allowed values: Percentage RPE RIR

workouts[].exercises[].intensityValuenumber

Intensity target.

workouts[].exercises[].tempoobject

Tempo in seconds per phase of the lift.

workouts[].exercises[].tempo.eccentricnumber
workouts[].exercises[].tempo.holdnumber
workouts[].exercises[].tempo.concentricnumber
workouts[].exercises[].tempo.restnumber
workouts[].exercises[].distancenumber

Distance for cardio exercises.

workouts[].exercises[].distanceUnitstring

Allowed values: meters km miles yards

workouts[].exercises[].specificAlternativeExercisesstring[]

Catalog ids the trainee may swap in for this exercise.

Request examples

cURL
curl --request POST \
  --url 'https://api.coach-platform.com/api/public/training-plans' \
  --header 'Authorization: Bearer cp_live_...' \
  --header 'Content-Type: application/json' \
  --data '{
  "title": "<string>"
}'
JavaScript (fetch)
const response = await fetch('https://api.coach-platform.com/api/public/training-plans', {
  method: 'POST',
  headers: {
    Authorization: 'Bearer cp_live_...',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "title": "<string>"
  }),
});

const data = await response.json();

Responses

201 Created

Example body (application/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>"
  },
  "warnings": [
    {
      "field": "<string>",
      "message": "<string>"
    }
  ]
}
Response fields (60)
Response fields
NameTypeDescription
dataobject
data.idstring
data.coachstring
data.escortsstring[]
data.titlestring
data.notesstring
data.levelstring

Allowed values: Beginner Intermediate Advanced

data.maxDurationnumber

Target session length in minutes.

data.isTemplateboolean

True when the plan is a reusable template.

data.workoutsobject[]
data.workouts[].trainingNamestring

Display name of the day, e.g. "Day A — Push".

data.workouts[].trainingTypestring

Day label, e.g. "A", "B", "FullBody".

Allowed values: A B C D E FullBody CrossFit Tabata HIIT EMOM AMRAP Circuit ForTime

data.workouts[].exerciseOrderstring

How exercises are performed. Defaults to Sequential.

Allowed values: Sequential Circuit Superset Complex

data.workouts[].notesstring

Free-text note for the whole day.

data.workouts[].timeBasedDetailsobject

Timing configuration for circuit-style days (Tabata, HIIT, EMOM, AMRAP). Seconds unless noted.

data.workouts[].timeBasedDetails.totalRoundsnumber
data.workouts[].timeBasedDetails.timeLimitnumber
data.workouts[].timeBasedDetails.workIntervalnumber
data.workouts[].timeBasedDetails.restIntervalnumber
data.workouts[].timeBasedDetails.restBetweenRoundsnumber
data.workouts[].exercisesobject[]
data.workouts[].exercises[].exerciseDetailsobject

The exercise catalog entry. On write, pass exerciseDetails as the catalog id string (from GET /exercises).

data.workouts[].exercises[].exerciseDetails.idstring
data.workouts[].exercises[].exerciseDetails.namestring
data.workouts[].exercises[].setsNumberstring

Number of sets, e.g. "3".

data.workouts[].exercises[].repsNumberstring

Reps per set, e.g. "10" or "8-12".

data.workouts[].exercises[].restTimestring

Rest between sets in seconds, e.g. "90".

data.workouts[].exercises[].isDurationBasedboolean

True for timed exercises (e.g. plank) instead of reps.

data.workouts[].exercises[].setDurationstring

Duration per set in seconds when isDurationBased is true.

data.workouts[].exercises[].weightPercentagenumber

Working weight as % of 1RM, e.g. 75.

data.workouts[].exercises[].customNotesstring

Free-text note shown to the trainee for this exercise.

data.workouts[].exercises[].weightnumber

Working weight in kg for the exercise.

data.workouts[].exercises[].setsobject[]

Per-set prescription. Takes precedence over setsNumber/repsNumber when present.

data.workouts[].exercises[].sets[].setNumbernumber

1-based position of the set.

data.workouts[].exercises[].sets[].repsstring

Reps for this set, e.g. "8".

data.workouts[].exercises[].sets[].weightnumber

Working weight for this set.

data.workouts[].exercises[].sets[].restTimestring

Rest after this set in seconds.

data.workouts[].exercises[].sets[].intensityValuenumber

Intensity for this set, read against intensityType.

data.workouts[].exercises[].sets[].dropSetstring

Allowed values: DropSet DoubleDropSet TripleDropSet

data.workouts[].exercises[].sets[].isWarmupSetboolean

Warmup sets are not counted towards working volume.

data.workouts[].exercises[].superSetboolean

True when this exercise belongs to a superset.

data.workouts[].exercises[].superSetGroupstring

Shared identifier grouping the exercises performed together in one superset.

data.workouts[].exercises[].dropSetstring

Allowed values: DropSet DoubleDropSet TripleDropSet

data.workouts[].exercises[].restPauseboolean

Rest-pause technique.

data.workouts[].exercises[].clusterboolean

Cluster-set technique.

data.workouts[].exercises[].trackingTypestring

What the trainee logs for this exercise.

Allowed values: weight_reps reps_only duration completion

data.workouts[].exercises[].intensityTypestring

How intensityValue is interpreted.

Allowed values: Percentage RPE RIR

data.workouts[].exercises[].intensityValuenumber

Intensity target.

data.workouts[].exercises[].tempoobject

Tempo in seconds per phase of the lift.

data.workouts[].exercises[].tempo.eccentricnumber
data.workouts[].exercises[].tempo.holdnumber
data.workouts[].exercises[].tempo.concentricnumber
data.workouts[].exercises[].tempo.restnumber
data.workouts[].exercises[].distancenumber

Distance for cardio exercises.

data.workouts[].exercises[].distanceUnitstring

Allowed values: meters km miles yards

data.workouts[].exercises[].specificAlternativeExercisesstring[]

Catalog ids the trainee may swap in for this exercise.

data.createdAtstring<date-time>
warningsobject[]

Non-fatal problems with follow-up writes. The resource was created, but each listed field was not applied.

warnings[].fieldstring
warnings[].messagestring

Error responses

  • 400 Bad Request
  • 401 Unauthorized
  • 403 Forbidden
  • 404 Not Found
  • 409 Conflict
  • 429 Too Many Requests
  • 500 Internal Server Error

These statuses share the same response body.

Example body (application/json)
{
  "error": {
    "code": "<string>",
    "message": "<string>",
    "fix": "<string>",
    "details": {},
    "retryAfterMs": 123
  }
}
Error fields (6)
Error fields
NameTypeDescription
errorobject
error.codestring
error.messagestring
error.fixstring | null
error.detailsobject | null
error.retryAfterMsnumber | null

This page is also available as Markdown. Browse it in the interactive explorer or download the OpenAPI spec.