curl -X PUT "https://api.fitsociety.io/public/v1/workout/performance/sessions/{sessionId}/exercises/{exerciseId}" \
-H "Authorization: Bearer <access_token>" \
-H "Idempotency-Key: <stable_request_key>" \
-H "Content-Type: application/json" \
-d '{}'const options = {
method: 'PUT',
headers: {
'Idempotency-Key': '<idempotency-key>',
Authorization: 'Bearer <token>',
'Content-Type': 'application/json'
},
body: JSON.stringify({})
};
fetch('https://api.fitsociety.io/public/v1/workout/performance/sessions/{sessionId}/exercises/{exerciseId}', 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/sessions/{sessionId}/exercises/{exerciseId}"
payload = {}
headers = {
"Idempotency-Key": "<idempotency-key>",
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.put(url, json=payload, headers=headers)
print(response.text)Upsert exercise performance for a workout v2 session
The response includes resolved machine-setting definitions and historical values. Omitted machine-setting fields remain unchanged; an explicit empty string clears that setting without restoring its previous value. Response targets and RPE visibility use the freshly loaded plan after any saved prescription updates.
addedSets returns the saved sets in the SAME shape the day / WOD / freestyle GET screens use — including the per-set previous column and the placeholder fields below — so the client can re-render straight off this response without re-fetching. (sets remains the stored rows.)
- Per-set placeholders. Every
sets[]row carries twelve extra fields:repsPlaceholderCoach,weightPlaceholderCoach,timeSecondsPlaceholderCoach,distanceMetersPlaceholderCoach,rpePlaceholderCoach,restSecondsPlaceholderCoach, and the same six without theCoachsuffix. A placeholder is the greyed hint an EMPTY input shows. ·*PlaceholderCoachis the coach’s prescription for that set index (WorkoutPlansetsData).nullfor a metric this exercise type does not track (atimeexercise has no reps hint), for a set beyond the prescribed count, for a metric the coach left blank, and for freestyle, which has no prescription at all. ·*Placeholderis the hint to actually show. The coach wins: it is the*PlaceholderCoachvalue whenever there is one, and falls back to the client’s own stored placeholder only where the coach prescribed nothing. So it is identical to*PlaceholderCoachon any prescribed set, and always reflects the CURRENT prescription — a coach editing 8 reps to 12 changes the hint immediately, with no stale client copy able to outlive it. · The client’s stored placeholder (sent per set onPUT /app/v1/workout/performance/sessions/{sessionId}/exercises/{exerciseId}) is therefore a FALLBACK for sets the coach never prescribed, not an override. Send one only where*PlaceholderCoachisnull; sending it elsewhere is accepted but ignored. · Placeholders are never logged work — excluded from every total, volume, PR and completion check — so a hint left showing after a value is cleared does not make the set count. They are emitted on logged rows too, because clearing a logged value is exactly when the hint has to reappear. · Committing a hint. When the athlete marks a set completed without typing, send the shown NUMERIC hint as the real value ({ reps: 22, completed: true }); it then counts as their performance. A RANGE hint ("8-12") must NOT be submitted on a completed set — the client should prompt the athlete to enter a value and skip the call, because a range reaching a completed set is stored as its MINIMUM ("12-24"→ 12 reps, 120 volume), silently under-crediting them. An unticked set sends its metric asnullwithcompleted: falseand simply keeps showing the hint.
Requires the workout_sessions:write scope. This operation maps to /app/v1/workout/performance/sessions/:sessionId/exercises/:exerciseId 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 PUT "https://api.fitsociety.io/public/v1/workout/performance/sessions/{sessionId}/exercises/{exerciseId}" \
-H "Authorization: Bearer <access_token>" \
-H "Idempotency-Key: <stable_request_key>" \
-H "Content-Type: application/json" \
-d '{}'const options = {
method: 'PUT',
headers: {
'Idempotency-Key': '<idempotency-key>',
Authorization: 'Bearer <token>',
'Content-Type': 'application/json'
},
body: JSON.stringify({})
};
fetch('https://api.fitsociety.io/public/v1/workout/performance/sessions/{sessionId}/exercises/{exerciseId}', 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/sessions/{sessionId}/exercises/{exerciseId}"
payload = {}
headers = {
"Idempotency-Key": "<idempotency-key>",
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.put(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
Workout session id.
"67f1234567890abcdef1234"
Exercise id to upsert within the session.
"67f1234567890abcdef1234"
Body
All fields optional — send only what changes (e.g. just notes or restSeconds). When sets are sent, isCompleted is auto-derived (true only when every set is completed) and overrides any isCompleted in the body; an explicit isCompleted is honored only when no sets are sent.
Existing server performance id to update. A valid id that does not match an active performance in the authorized company, client, and session scope returns 404. For transitional mobile compatibility, a value equal to the URL exerciseId is treated as a catalog-id create fallback and is never adopted as the new performance id.
"67f1234567890abcdef1234"
Show child attributes
Show child attributes
Client's own exercise note (this session).
"Last set was slow."
Client's own exercise-level rest ("Rest time : 30 sec"). null/"" clears it.
30
Auto-derived from sets when they are sent; only honored explicitly when no sets are present.
true
"67f1234567890abcdef1234"
"67f1234567890abcdef1234"
"67f1234567890abcdef1234"
0
Show child attributes
Show child attributes
8Show child attributes
Show child attributes