curl -X POST "https://api.fitsociety.io/public/v1/workout/plans" \
-H "Authorization: Bearer <access_token>" \
-H "Idempotency-Key: <stable_request_key>" \
-H "Content-Type: application/json" \
-d '{}'const form = new FormData();
form.append('name', '[{"lang":"en","value":"4-Day Strength Block"},{"lang":"nl","value":"4-Daags Krachtblok"}]');
form.append('description', '[{"lang":"en","value":"Progressive upper/lower split."},{"lang":"nl","value":"Progressieve upper/lower split."}]');
const options = {
method: 'POST',
headers: {'Idempotency-Key': '<idempotency-key>', Authorization: 'Bearer <token>'}
};
options.body = form;
fetch('https://api.fitsociety.io/public/v1/workout/plans', 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/plans"
payload = "-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"name\"\r\n\r\n[{\"lang\":\"en\",\"value\":\"4-Day Strength Block\"},{\"lang\":\"nl\",\"value\":\"4-Daags Krachtblok\"}]\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"description\"\r\n\r\n[{\"lang\":\"en\",\"value\":\"Progressive upper/lower split.\"},{\"lang\":\"nl\",\"value\":\"Progressieve upper/lower split.\"}]\r\n-----011000010111000001101001--"
headers = {
"Idempotency-Key": "<idempotency-key>",
"Authorization": "Bearer <token>",
"Content-Type": "multipart/form-data; boundary=---011000010111000001101001"
}
response = requests.post(url, data=payload, headers=headers)
print(response.text)Create a workout v2 plan
Creates a workout plan in the authenticated company scope. Accepts multipart/form-data only — the route is gated by the workout media upload middleware. Send structured arrays and objects (e.g. name, description, duration, defaultExerciseValues) as JSON strings. Use imageFiles and videoFiles for media uploads; the legacy aliases images and videos are still accepted.
Requires the workout_plans:write scope. This operation maps to /app/v1/workout/plans 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/plans" \
-H "Authorization: Bearer <access_token>" \
-H "Idempotency-Key: <stable_request_key>" \
-H "Content-Type: application/json" \
-d '{}'const form = new FormData();
form.append('name', '[{"lang":"en","value":"4-Day Strength Block"},{"lang":"nl","value":"4-Daags Krachtblok"}]');
form.append('description', '[{"lang":"en","value":"Progressive upper/lower split."},{"lang":"nl","value":"Progressieve upper/lower split."}]');
const options = {
method: 'POST',
headers: {'Idempotency-Key': '<idempotency-key>', Authorization: 'Bearer <token>'}
};
options.body = form;
fetch('https://api.fitsociety.io/public/v1/workout/plans', 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/plans"
payload = "-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"name\"\r\n\r\n[{\"lang\":\"en\",\"value\":\"4-Day Strength Block\"},{\"lang\":\"nl\",\"value\":\"4-Daags Krachtblok\"}]\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"description\"\r\n\r\n[{\"lang\":\"en\",\"value\":\"Progressive upper/lower split.\"},{\"lang\":\"nl\",\"value\":\"Progressieve upper/lower split.\"}]\r\n-----011000010111000001101001--"
headers = {
"Idempotency-Key": "<idempotency-key>",
"Authorization": "Bearer <token>",
"Content-Type": "multipart/form-data; boundary=---011000010111000001101001"
}
response = requests.post(url, data=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"
Body
Multipart JSON string for WorkoutLocalizedTextArray.
"[{\"lang\":\"en\",\"value\":\"4-Day Strength Block\"},{\"lang\":\"nl\",\"value\":\"4-Daags Krachtblok\"}]"
Multipart JSON string for WorkoutLocalizedTextArray.
"[{\"lang\":\"en\",\"value\":\"Progressive upper/lower split.\"},{\"lang\":\"nl\",\"value\":\"Progressieve upper/lower split.\"}]"
Agility & Speed, Strength, Hypertrophy, Strength - Dynamic, Strength - Explosive, Strength - Static, Cardio, Cardio - Duration, Cardio - Interval, HIIT, Crossfit, Mobility, Stretching, Core, Coordination & Balance, Movement Prep, Functional Training, Recovery, Sports, Martial Arts, Yoga, Other, GROUP_WORKOUT_TEMPLATE, Plyometric, Cycling, Cardio - interval, Mobilisation, Stabilisation, Daily Living, Target zone, Respiration, Breathing, Massage, Outdoor sports, Indoor sports, Dance, Group Lessons, Martial arts, Material Arts, Fight skills, Mind and body, , default "Strength"
Weight Loss, Hypertrophy, Endurance, Flexibility, General Fitness, Strength "Strength"
Linear, Non-Linear, Block, Undulating, None "Linear"
Beginner, Intermediate, Advanced "Intermediate"
Low, Medium, High "High"
Everyone, Company, Fitsociety "Company"
Active, Inactive, Draft "Draft"
Template, Assigned "Template"
Multipart JSON string for WorkoutPlanDuration.
"{\"startDate\":\"2026-04-15\",\"endDate\":\"2026-06-15\",\"hasEndDate\":true}"
Multipart JSON string for WorkoutProgrammeTemplateSchedule. Multi-week library templates always store a blank start date; the assignment supplies the real start date.
"{\"mode\":\"multi_week\",\"releasePolicy\":\"weekly_from_start\",\"startDate\":\"\"}"
Whether new exercises use plan-level defaults. Send false with defaultExerciseValues set to the JSON literal null to disable defaults.
true, false "true"
Multipart JSON string for WorkoutDefaultExerciseValues, or the JSON literal null when useDefaultExerciseSettings is false.
"{\"sets\":4,\"reps\":8,\"rest\":120,\"weight\":0,\"repTempo\":\"3010\",\"duration\":0}"
Multipart JSON string for WorkoutImageAsset[]. Combine with uploaded imageFiles when you want to attach uploaded media.
"[{\"url\":\"https://cdn.example.com/workouts/plan-cover.jpg\",\"width\":1280,\"height\":720}]"
Multipart JSON string for WorkoutVideoAsset[]. Combine with uploaded videoFiles when you want to attach uploaded media.
"[{\"url\":\"https://cdn.example.com/workouts/plan-demo.mp4\",\"platform\":\"s3\",\"videoId\":\"\",\"thumbnail\":\"https://cdn.example.com/workouts/plan-demo-thumb.jpg\",\"duration\":95,\"width\":1920,\"height\":1080}]"
Preferred binary fields are imageFiles and videoFiles. Legacy binary aliases images and videos remain accepted by the backend for backward compatibility. Structured array/object fields must be sent as JSON strings. Max 5 image files.
5Preferred binary fields are imageFiles and videoFiles. Legacy binary aliases images and videos remain accepted by the backend for backward compatibility. Structured array/object fields must be sent as JSON strings. Max 3 video files.
3Response
Workout plan created. Runtime still returns the standard response envelope without a data property.