curl -X POST "https://api.fitsociety.io/public/v1/workout/performance/plans/{planId}/moments/{planMomentId}/exercises/{planExerciseId}/swap" \
-H "Authorization: Bearer <access_token>" \
-H "Idempotency-Key: <stable_request_key>" \
-H "Content-Type: application/json" \
-d '{}'const options = {
method: 'POST',
headers: {
'Idempotency-Key': '<idempotency-key>',
Authorization: 'Bearer <token>',
'Content-Type': 'application/json'
},
body: JSON.stringify({newExerciseId: '67f1234567890abcdef4321'})
};
fetch('https://api.fitsociety.io/public/v1/workout/performance/plans/{planId}/moments/{planMomentId}/exercises/{planExerciseId}/swap', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));import requests
url = "https://api.fitsociety.io/public/v1/workout/performance/plans/{planId}/moments/{planMomentId}/exercises/{planExerciseId}/swap"
payload = { "newExerciseId": "67f1234567890abcdef4321" }
headers = {
"Idempotency-Key": "<idempotency-key>",
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)Swap a prescribed plan-day exercise (by plan exercise id)
Swaps the exercise prescribed at a plan-day slot on the client’s ASSIGNED plan, keyed by planExerciseId. Plan-scoped (not session-scoped): it works from the plan-day screen even before anything is logged — no session or performance is required, so it covers the case where performanceId is null. Started or recorded performances keep their original exercise, type, sets and targets, including active sessions. Only untouched placeholders (no sets, completion, start or end) can be repointed or discarded for the replacement. The prescribed exercise on the assigned plan is repointed at newExerciseId. Coaches act for a client by passing clientId.
Requires the workout_sessions:write scope. This operation maps to /app/v1/workout/performance/plans/:planId/moments/:planMomentId/exercises/:planExerciseId/swap and retains its Workout V2 permission, feature-flag, and resource-scope checks.
The clientId path parameter is resolved inside the company bound to the Public API token when present.
curl -X POST "https://api.fitsociety.io/public/v1/workout/performance/plans/{planId}/moments/{planMomentId}/exercises/{planExerciseId}/swap" \
-H "Authorization: Bearer <access_token>" \
-H "Idempotency-Key: <stable_request_key>" \
-H "Content-Type: application/json" \
-d '{}'const options = {
method: 'POST',
headers: {
'Idempotency-Key': '<idempotency-key>',
Authorization: 'Bearer <token>',
'Content-Type': 'application/json'
},
body: JSON.stringify({newExerciseId: '67f1234567890abcdef4321'})
};
fetch('https://api.fitsociety.io/public/v1/workout/performance/plans/{planId}/moments/{planMomentId}/exercises/{planExerciseId}/swap', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));import requests
url = "https://api.fitsociety.io/public/v1/workout/performance/plans/{planId}/moments/{planMomentId}/exercises/{planExerciseId}/swap"
payload = { "newExerciseId": "67f1234567890abcdef4321" }
headers = {
"Idempotency-Key": "<idempotency-key>",
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)Authorizations
Public API access token issued by /public/v1/oauth/token. Example: Authorization: Bearer fspt_.... Each resource request rechecks the token company's current provider access. Disabling access blocks existing tokens with 403 auth.provider_unavailable.
Headers
Required for Public API write requests. Reusing the same key with the same method, path, and body replays the stored successful response; reusing it with a different request returns 409 idempotency.conflict.
1 - 200"booking-create-20260714-001"
Path Parameters
Assigned plan (template) id.
"67f1234567890abcdef1234"
Plan day (moment) id.
"67f1234567890abcdef1234"
The prescribed exercise subdoc id (the plan-day slot) to swap.
"67f1234567890abcdef1234"