curl -X POST "https://api.fitsociety.io/public/v1/workout/clients/{clientId}/plans/template/{templateId}/weeks/{weekNumber}/days/{weekday}/workouts" \
-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({name: 'Leg Day'})
};
fetch('https://api.fitsociety.io/public/v1/workout/clients/{clientId}/plans/template/{templateId}/weeks/{weekNumber}/days/{weekday}/workouts', 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/clients/{clientId}/plans/template/{templateId}/weeks/{weekNumber}/days/{weekday}/workouts"
payload = { "name": "Leg Day" }
headers = {
"Idempotency-Key": "<idempotency-key>",
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text){
"data": {
"planId": "67f1234567890abcdef1234",
"weekNumber": 1,
"weekday": 1,
"planMomentId": "67f1234567890abcdef1234",
"planMoment": {
"_id": "67f1234567890abcdef1234",
"name": [
{
"lang": "en",
"value": "Full Body Strength"
},
{
"lang": "nl",
"value": "Full Body Kracht"
}
],
"description": [
{
"lang": "en",
"value": "Full Body Strength"
},
{
"lang": "nl",
"value": "Full Body Kracht"
}
],
"dayOrder": 1,
"order": 0,
"weekNumber": 1,
"sessionOrder": 1,
"date": "2026-04-15",
"scoringType": "forTime",
"targetValue": null,
"descriptionOnly": true,
"timeDomain": {
"windowSeconds": null,
"timeCapSeconds": 900,
"rounds": 3,
"intervalSeconds": null,
"intervalCount": null,
"workSeconds": null,
"restSeconds": null
},
"scoreValidation": {
"timeCapSeconds": 900,
"maxReps": null,
"expectedIntervals": null
},
"exercises": [
{
"_id": "67f1234567890abcdef1234",
"exerciseId": "67f1234567890abcdef1234",
"type": "reps",
"minReps": 6,
"maxReps": 8,
"notes": "Pause 1 second on the chest.",
"sets": 4,
"metric": "kg",
"rest": 120,
"intensity": "High",
"difficulty": "Intermediate",
"groupId": "super-a",
"rpe": 8,
"rmPercentage": 75,
"tempo": "3010",
"perSetTrackingEnabled": true,
"rpeEnabled": false,
"showSetRpe": true,
"setsData": [
{
"setNumber": 1,
"setType": "N",
"metric": "12",
"repetition": "8-12",
"weight": 60,
"rest": 90,
"rpe": 7.5,
"duration": 45,
"distance": 250
}
],
"setupSettingsEnabled": true,
"setupFields": [
{
"key": "seat",
"label": "Seat",
"type": "text",
"unit": "",
"options": [
"1",
"2",
"3",
"4"
],
"order": 1,
"required": false,
"enabled": true,
"defaultValue": 3
}
],
"order": 0,
"isDeleted": false,
"deletedAt": null,
"createdAt": "2026-04-12T10:00:00.000Z",
"updatedAt": "2026-04-12T10:00:00.000Z"
}
],
"isDeleted": false,
"deletedAt": null,
"createdAt": "2026-04-12T10:00:00.000Z",
"updatedAt": "2026-04-12T10:00:00.000Z"
},
"item": {
"_id": "67f1234567890abcdef1234",
"order": 1,
"startTime": "08:30",
"plannedDurationMinutes": 45,
"status": "today",
"plannedDate": "2026-09-08",
"scheduledDate": "2026-09-08",
"source": "programme",
"clientAdjustment": {
"action": "moved",
"occurrenceDate": "2026-09-08",
"scheduledDate": "2026-09-08",
"changedAt": null
},
"canClientRestore": true,
"type": "workout",
"planMomentId": "67f1234567890abcdef1234"
},
"itemCount": 2,
"items": [
{
"_id": "67f1234567890abcdef1234",
"order": 1,
"startTime": "08:30",
"plannedDurationMinutes": 45,
"status": "today",
"plannedDate": "2026-09-08",
"scheduledDate": "2026-09-08",
"source": "programme",
"clientAdjustment": {
"action": "moved",
"occurrenceDate": "2026-09-08",
"scheduledDate": "2026-09-08",
"changedAt": null
},
"canClientRestore": true,
"type": "workout",
"planMomentId": "67f1234567890abcdef1234"
}
]
},
"meta": {
"requestId": "4f849d7d-f4f1-45cc-b4b7-3984a3d17f83",
"rateLimit": {
"limit": 10,
"remaining": 9,
"resetSeconds": 1
},
"idempotency": {
"replayed": false
}
}
}Add a workout to one day of the client's plan
Adds ONE workout to one day of the client’s own programme — the “Freestyle Workout Plan” option on the day sheet.
A workout is two things at once: a PLAN MOMENT (which holds the exercises) and a SCHEDULE ITEM pointing at it. The coach-side createPlanMoment needs a separate schedulePlacement block; here the placement IS the URL, so one call creates both and they can never be written out of step.
The item is appended LAST on that day, so nothing already scheduled shifts position. An unauthored weekday is created on demand. A REST day is refused (WORKOUTPLAN_ITEM_ADD_REST_DAY).
Requires the workout_client_plans:write scope. This operation maps to /app/v1/workout/plans/client/template/:templateId/weeks/:weekNumber/days/:weekday/workouts and retains its Workout V2 permission, feature-flag, and resource-scope checks.
The clientId path parameter identifies the client represented by the request context.
curl -X POST "https://api.fitsociety.io/public/v1/workout/clients/{clientId}/plans/template/{templateId}/weeks/{weekNumber}/days/{weekday}/workouts" \
-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({name: 'Leg Day'})
};
fetch('https://api.fitsociety.io/public/v1/workout/clients/{clientId}/plans/template/{templateId}/weeks/{weekNumber}/days/{weekday}/workouts', 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/clients/{clientId}/plans/template/{templateId}/weeks/{weekNumber}/days/{weekday}/workouts"
payload = { "name": "Leg Day" }
headers = {
"Idempotency-Key": "<idempotency-key>",
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text){
"data": {
"planId": "67f1234567890abcdef1234",
"weekNumber": 1,
"weekday": 1,
"planMomentId": "67f1234567890abcdef1234",
"planMoment": {
"_id": "67f1234567890abcdef1234",
"name": [
{
"lang": "en",
"value": "Full Body Strength"
},
{
"lang": "nl",
"value": "Full Body Kracht"
}
],
"description": [
{
"lang": "en",
"value": "Full Body Strength"
},
{
"lang": "nl",
"value": "Full Body Kracht"
}
],
"dayOrder": 1,
"order": 0,
"weekNumber": 1,
"sessionOrder": 1,
"date": "2026-04-15",
"scoringType": "forTime",
"targetValue": null,
"descriptionOnly": true,
"timeDomain": {
"windowSeconds": null,
"timeCapSeconds": 900,
"rounds": 3,
"intervalSeconds": null,
"intervalCount": null,
"workSeconds": null,
"restSeconds": null
},
"scoreValidation": {
"timeCapSeconds": 900,
"maxReps": null,
"expectedIntervals": null
},
"exercises": [
{
"_id": "67f1234567890abcdef1234",
"exerciseId": "67f1234567890abcdef1234",
"type": "reps",
"minReps": 6,
"maxReps": 8,
"notes": "Pause 1 second on the chest.",
"sets": 4,
"metric": "kg",
"rest": 120,
"intensity": "High",
"difficulty": "Intermediate",
"groupId": "super-a",
"rpe": 8,
"rmPercentage": 75,
"tempo": "3010",
"perSetTrackingEnabled": true,
"rpeEnabled": false,
"showSetRpe": true,
"setsData": [
{
"setNumber": 1,
"setType": "N",
"metric": "12",
"repetition": "8-12",
"weight": 60,
"rest": 90,
"rpe": 7.5,
"duration": 45,
"distance": 250
}
],
"setupSettingsEnabled": true,
"setupFields": [
{
"key": "seat",
"label": "Seat",
"type": "text",
"unit": "",
"options": [
"1",
"2",
"3",
"4"
],
"order": 1,
"required": false,
"enabled": true,
"defaultValue": 3
}
],
"order": 0,
"isDeleted": false,
"deletedAt": null,
"createdAt": "2026-04-12T10:00:00.000Z",
"updatedAt": "2026-04-12T10:00:00.000Z"
}
],
"isDeleted": false,
"deletedAt": null,
"createdAt": "2026-04-12T10:00:00.000Z",
"updatedAt": "2026-04-12T10:00:00.000Z"
},
"item": {
"_id": "67f1234567890abcdef1234",
"order": 1,
"startTime": "08:30",
"plannedDurationMinutes": 45,
"status": "today",
"plannedDate": "2026-09-08",
"scheduledDate": "2026-09-08",
"source": "programme",
"clientAdjustment": {
"action": "moved",
"occurrenceDate": "2026-09-08",
"scheduledDate": "2026-09-08",
"changedAt": null
},
"canClientRestore": true,
"type": "workout",
"planMomentId": "67f1234567890abcdef1234"
},
"itemCount": 2,
"items": [
{
"_id": "67f1234567890abcdef1234",
"order": 1,
"startTime": "08:30",
"plannedDurationMinutes": 45,
"status": "today",
"plannedDate": "2026-09-08",
"scheduledDate": "2026-09-08",
"source": "programme",
"clientAdjustment": {
"action": "moved",
"occurrenceDate": "2026-09-08",
"scheduledDate": "2026-09-08",
"changedAt": null
},
"canClientRestore": true,
"type": "workout",
"planMomentId": "67f1234567890abcdef1234"
}
]
},
"meta": {
"requestId": "4f849d7d-f4f1-45cc-b4b7-3984a3d17f83",
"rateLimit": {
"limit": 10,
"remaining": 9,
"resetSeconds": 1
},
"idempotency": {
"replayed": false
}
}
}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 workout plan id.
"67f1234567890abcdef1234"
The 1-based week of the calendar schedule.
x >= 11
1 = Monday … 7 = Sunday.
1 <= x <= 71
Client in the company bound to the Public API token.
^[a-fA-F0-9]{24}$Body
A plain string is wrapped in the caller's language. An already-localized [{lang, value}] array is also accepted.
"Leg Day"
"Quads and glutes."
Optional — the day can be created empty and filled later.
Optional 24-hour HH:mm.
"12:23"
32
Coach tokens only; see the add-week endpoint.