---
title: "POST /api/public/training-plans/{planId}/duplicate"
method: POST
path: "/api/public/training-plans/{planId}/duplicate"
tags: ["Training Plans"]
---

# POST /api/public/training-plans/{planId}/duplicate

`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). This is the supported way to turn a template into a trainee plan, because a template itself can never be assigned: you assign the copy, not the template. The copy is always created with isTemplate=false and isEditable=true. Returns the new plan, including its id, so no follow-up lookup is needed.

Assignment is optional. Send neither traineeId nor escortId to get an unassigned copy; send one of them to duplicate and assign in a single call.

## Path parameters

- `planId` string, required

## Headers

- `Idempotency-Key` string

## Request body

- object, nullable
  - `traineeId` string — Assign the copy to this trainee by resolving their coaching period automatically. Picks the trainee's most recently created Active escort. Returns 400 'No active escort found for this trainee' when the trainee has no Active escort, which is the case for Pending, Suspended, or not-yet-started periods; use escortId for those. Omit both ids to leave the copy unassigned.
  - `escortId` string — Assign the copy to this exact coaching period, whatever its status. Use this instead of traineeId when the target period is not Active (Pending, Suspended, or scheduled for the future), or when the trainee has more than one period and you must not rely on automatic selection. When both ids are sent, escortId decides the target and traineeId is only validated against it: a mismatch returns 400 "escortId does not belong to the provided traineeId", which is a useful safety check when the two ids come from an external system.
  - `title` string — Title for the copy. Defaults to the source title with a copy suffix. The copy stays in the same plan group as its source, so duplicates do not scatter the plans list.

## Response `201`

Created

- object — Created
  - `data` object
    - `id` string
    - `coach` string
    - `escorts` string[]
    - `title` string
    - `notes` string
    - `level` 'Beginner' | 'Intermediate' | 'Advanced'
    - `maxDuration` number — Target session length in minutes.
    - `isTemplate` boolean — True when the plan is a reusable template.
    - `workouts` object[]
      - `trainingName` string — Display name of the day, e.g. "Day A — Push".
      - `trainingType` 'A' | 'B' | 'C' | 'D' | 'E' | 'FullBody' | 'CrossFit' | 'Tabata' | 'HIIT' | 'EMOM' | 'AMRAP' | 'Circuit' | 'ForTime' — Day label, e.g. "A", "B", "FullBody".
      - `exerciseOrder` 'Sequential' | 'Circuit' | 'Superset' | 'Complex' — How exercises are performed. Defaults to Sequential.
      - `notes` string — Free-text note for the whole day.
      - `timeBasedDetails` object — Timing configuration for circuit-style days (Tabata, HIIT, EMOM, AMRAP). Seconds unless noted.
        - `totalRounds` number
        - `timeLimit` number
        - `workInterval` number
        - `restInterval` number
        - `restBetweenRounds` number
      - `exercises` object[]
        - `exerciseDetails` object — The exercise catalog entry. On write, pass exerciseDetails as the catalog id string (from GET /exercises).
          - `id` string
          - `name` string
        - `setsNumber` string — Number of sets, e.g. "3".
        - `repsNumber` string — Reps per set, e.g. "10" or "8-12".
        - `restTime` string — Rest between sets in seconds, e.g. "90".
        - `isDurationBased` boolean — True for timed exercises (e.g. plank) instead of reps.
        - `setDuration` string — Duration per set in seconds when isDurationBased is true.
        - `weightPercentage` number — Working weight as % of 1RM, e.g. 75.
        - `customNotes` string — Free-text note shown to the trainee for this exercise.
        - `weight` number — Working weight in kg for the exercise.
        - `sets` object[] — Per-set prescription. Takes precedence over setsNumber/repsNumber when present.
          - `setNumber` number — 1-based position of the set.
          - `reps` string — Reps for this set, e.g. "8".
          - `weight` number — Working weight for this set.
          - `restTime` string — Rest after this set in seconds.
          - `intensityValue` number — Intensity for this set, read against intensityType.
          - `dropSet` 'DropSet' | 'DoubleDropSet' | 'TripleDropSet'
          - `isWarmupSet` boolean — Warmup sets are not counted towards working volume.
        - `superSet` boolean — True when this exercise belongs to a superset.
        - `superSetGroup` string — Shared identifier grouping the exercises performed together in one superset.
        - `dropSet` 'DropSet' | 'DoubleDropSet' | 'TripleDropSet'
        - `restPause` boolean — Rest-pause technique.
        - `cluster` boolean — Cluster-set technique.
        - `trackingType` 'weight_reps' | 'reps_only' | 'duration' | 'completion' — What the trainee logs for this exercise.
        - `intensityType` 'Percentage' | 'RPE' | 'RIR' — How intensityValue is interpreted.
        - `intensityValue` number — Intensity target.
        - `tempo` object — Tempo in seconds per phase of the lift.
          - `eccentric` number
          - `hold` number
          - `concentric` number
          - `rest` number
        - `distance` number — Distance for cardio exercises.
        - `distanceUnit` 'meters' | 'km' | 'miles' | 'yards'
        - `specificAlternativeExercises` string[] — Catalog ids the trainee may swap in for this exercise.
    - `createdAt` string, date-time
  - `warnings` object[] — Non-fatal problems with follow-up writes. The resource was created, but each listed field was not applied.
    - `field` string
    - `message` string

## Other responses

- `400` — Default Response
- `401` — Default Response
- `403` — Default Response
- `404` — Default Response
- `409` — Default Response
- `429` — Default Response
- `500` — Default Response

---

[API](https://skmtc.dev/coach-platform/apis/coach-platform-api.md) · [All operations](https://skmtc.dev/coach-platform/apis/coach-platform-api/llms.txt) · [OpenAPI document](https://skmtc-service-production.skmtc.workers.dev/v1/apis/coach-platform/coach-platform-api/revisions/3d18b514ae8c/schema)
