Training
A customer's workouts with their full tree of blocks, sets and repetitions, across their whole history.
Start with the workout data model. The list returns workouts without their content: fetch a single workout for the full tree.
List workouts
Athlete workouts of your organization, without their content. Templates are excluded; they are served by /templates/workouts. Fetch a workout’s blocks, sets and metrics with GET /workouts/{id}.
Request
curl "https://api-integrations-next.vitruve.fit/v1/workouts?userId=3b9c1e24-6f7a-4d2b-8c15-a40e9d7f1b62&from=2026-03-01&to=2026-03-31&limit=2" \
-H "X-API-Key: CUSTOMER_INTEGRATION_KEY" \
-H "X-Partner-Secret: YOUR_PARTNER_SECRET"const response = await fetch(
'https://api-integrations-next.vitruve.fit/v1/workouts?userId=3b9c1e24-6f7a-4d2b-8c15-a40e9d7f1b62&from=2026-03-01&to=2026-03-31&limit=2',
{
headers: {
'X-API-Key': customer.vitruveKey,
'X-Partner-Secret': process.env.VITRUVE_PARTNER_SECRET
}
}
)
const body = await response.json()
if (!response.ok) throw new Error(`${response.status}: ${body.message}`)
console.log(body.data)import os
import requests
response = requests.get(
"https://api-integrations-next.vitruve.fit/v1/workouts?userId=3b9c1e24-6f7a-4d2b-8c15-a40e9d7f1b62&from=2026-03-01&to=2026-03-31&limit=2",
headers={
"X-API-Key": customer.vitruve_key,
"X-Partner-Secret": os.environ["VITRUVE_PARTNER_SECRET"],
},
timeout=30,
)
response.raise_for_status()
print(response.json()["data"])Response 200
{
"ok": true,
"timestamp": 1738247125207,
"data": [
{
"id": "f2c8e1a6-3d49-4b75-8e02-6a9d4c1b7f35",
"name": "Lower body strength A",
"isTemplate": false,
"rpe": 8,
"userId": "3b9c1e24-6f7a-4d2b-8c15-a40e9d7f1b62",
"organizationId": "7d3f8a52-1c0e-4b8e-9f4a-2e6b1d9c5a10",
"scheduledWorkoutId": null,
"completedAt": "2026-03-17T10:41:52.318Z",
"startedAt": "2026-03-17T09:58:04.902Z",
"createdAt": "2026-03-10T16:20:11.004Z",
"updatedAt": "2026-03-17T10:41:52.877Z",
"deletedAt": null,
"testType": null
},
{
"id": "5a0e7c3d-91b4-4f26-8d58-c3e2a1f6b097",
"name": "Upper body power",
"isTemplate": false,
"rpe": 7,
"userId": "3b9c1e24-6f7a-4d2b-8c15-a40e9d7f1b62",
"organizationId": "7d3f8a52-1c0e-4b8e-9f4a-2e6b1d9c5a10",
"scheduledWorkoutId": null,
"completedAt": "2026-03-14T17:32:10.441Z",
"startedAt": "2026-03-14T16:55:47.019Z",
"createdAt": "2026-03-14T16:55:46.870Z",
"updatedAt": "2026-03-14T17:32:10.902Z",
"deletedAt": null,
"testType": null
}
],
"meta": {
"limit": 2,
"hasMore": true,
"nextCursor": "ZTU2M2ZlYzMtM2IxYy00…"
}
}Query parameters
userIduuid- Only workouts of this athlete.
exerciseIduuid- Only workouts with at least one prescription of this exercise.
statuscompleted | pendingdefaultcompletedcompletedfor performed workouts,pendingfor assigned workouts not yet performed.trainingTypevbt | jump | sprint | reaction- Only workouts with at least one prescription of this training type.
fromdate- Inclusive lower bound:
YYYY-MM-DDor an ISO 8601 datetime. A bare date starts at 00:00 intimezone, or at the change on a day whose midnight a daylight-saving change skips. todate- Inclusive upper bound:
YYYY-MM-DDor an ISO 8601 datetime. A bare date covers that whole day intimezone. timezonestringdefaultUTC- IANA time zone, such as
Europe/Madrid. A barefrom/todate covers that whole day in it, and a datetime without an offset is a wall-clock time in it. Datetimes withZor an offset are taken as given. limitintegerdefault50- Page size, 1–100.
cursorstringmeta.nextCursorfrom the previous page. Omit it to get the first page.
Notes
- Completed workouts are sorted and filtered by
completedAt; pending workouts bycreatedAt. Both are newest first. from/totherefore apply tocompletedAtwhenstatus=completedand tocreatedAtwhenstatus=pending.trainingType=vbtalso matches prescriptions written beforetrainingTypeexisted, which carrytrainingType: nullandisVbt: true.- With both
exerciseIdandtrainingType, the same prescription must match both: a prescription of that exercise and that training type. - Workouts older than the history your plan includes are left out.
Response fields
iduuid- Workout id. Pass it to
GET /workouts/{id}for the full tree. namestring- Workout name.
userIduuid- Athlete the workout belongs to. Resolve with
GET /athletes/{id}. completedAtdatetimenullable- When the athlete finished the workout.
nullfor pending workouts. startedAtdatetimenullable- When the athlete started the workout.
rpeintegernullable- Session RPE entered by the athlete.
scheduledWorkoutIduuidnullable- Program slot this workout is attached to (
phases[].scheduledWorkouts[].idinGET /programs/{id}). Unique: one workout per slot. testTypestringnullable- Set when the workout is a standardized test (for example
vertical_jump,t_test), otherwisenull. createdAtdatetime- When the workout was created or assigned.
Failures are described in error codes.
Get a workout
One athlete workout with its full tree: blocks → prescriptions groups → prescriptions → serie prescriptions and series, each series with its values, set-level metrics and repetitions. Templates return 404 here; use GET /templates/workouts/{id}.
Request
curl "https://api-integrations-next.vitruve.fit/v1/workouts/f2c8e1a6-3d49-4b75-8e02-6a9d4c1b7f35" \
-H "X-API-Key: CUSTOMER_INTEGRATION_KEY" \
-H "X-Partner-Secret: YOUR_PARTNER_SECRET"const response = await fetch(
'https://api-integrations-next.vitruve.fit/v1/workouts/f2c8e1a6-3d49-4b75-8e02-6a9d4c1b7f35',
{
headers: {
'X-API-Key': customer.vitruveKey,
'X-Partner-Secret': process.env.VITRUVE_PARTNER_SECRET
}
}
)
const body = await response.json()
if (!response.ok) throw new Error(`${response.status}: ${body.message}`)
console.log(body.data)import os
import requests
response = requests.get(
"https://api-integrations-next.vitruve.fit/v1/workouts/f2c8e1a6-3d49-4b75-8e02-6a9d4c1b7f35",
headers={
"X-API-Key": customer.vitruve_key,
"X-Partner-Secret": os.environ["VITRUVE_PARTNER_SECRET"],
},
timeout=30,
)
response.raise_for_status()
print(response.json()["data"])Response 200
{
"ok": true,
"timestamp": 1738247125207,
"data": {
"id": "f2c8e1a6-3d49-4b75-8e02-6a9d4c1b7f35",
"name": "Lower body strength A",
"isTemplate": false,
"rpe": 8,
"userId": "3b9c1e24-6f7a-4d2b-8c15-a40e9d7f1b62",
"organizationId": "7d3f8a52-1c0e-4b8e-9f4a-2e6b1d9c5a10",
"scheduledWorkoutId": null,
"completedAt": "2026-03-17T10:41:52.318Z",
"startedAt": "2026-03-17T09:58:04.902Z",
"createdAt": "2026-03-10T16:20:11.004Z",
"updatedAt": "2026-03-17T10:41:52.877Z",
"deletedAt": null,
"testType": null,
"blocks": [
{
"id": "0b6d3a9e-7f21-4c58-9d14-e82a5f3c6b79",
"name": "Main lifts",
"isTemplate": false,
"order": 0,
"workoutId": "f2c8e1a6-3d49-4b75-8e02-6a9d4c1b7f35",
"organizationId": "7d3f8a52-1c0e-4b8e-9f4a-2e6b1d9c5a10",
"createdAt": "2026-03-10T16:20:11.004Z",
"updatedAt": "2026-03-10T16:20:11.004Z",
"deletedAt": null,
"prescriptionsGroups": [
{
"id": "4d1e8c75-2a93-4f06-b8e7-39c0a6d5f142",
"name": "Back squat",
"isTemplate": false,
"order": 0,
"blockId": "0b6d3a9e-7f21-4c58-9d14-e82a5f3c6b79",
"organizationId": "7d3f8a52-1c0e-4b8e-9f4a-2e6b1d9c5a10",
"createdAt": "2026-03-10T16:20:11.004Z",
"updatedAt": "2026-03-10T16:20:11.004Z",
"deletedAt": null,
"prescriptions": [
{
"id": "a7f35c20-9e18-4d64-8b3a-5c2d0e91f6b8",
"organizationId": "7d3f8a52-1c0e-4b8e-9f4a-2e6b1d9c5a10",
"exerciseId": "1c7a5e93-f804-4b26-a9d1-3e6b0d82c4f7",
"prescriptionsGroupId": "4d1e8c75-2a93-4f06-b8e7-39c0a6d5f142",
"name": "Back squat",
"order": 0,
"note": "Stop the set if mean velocity drops below 0.55 m/s.",
"isTemplate": false,
"isVbt": true,
"trainingType": "vbt",
"trainingAlgorithm": null,
"createdAt": "2026-03-10T16:20:11.004Z",
"updatedAt": "2026-03-10T16:20:11.004Z",
"deletedAt": null,
"seriePrescriptions": [
{
"id": "e3b72d05-6a1c-4f98-b4e3-1d8c9a5f0726",
"organizationId": "7d3f8a52-1c0e-4b8e-9f4a-2e6b1d9c5a10",
"prescriptionId": "a7f35c20-9e18-4d64-8b3a-5c2d0e91f6b8",
"prescriptionTypeId": "3e9d7a12-c5b8-4f40-a136-d82e4b6f9c05",
"unitId": "fa52c8d4-17e3-4b69-8d0a-6c3f9e1b2a78",
"order": 0,
"isRange": false,
"isTopMode": false,
"createdAt": "2026-03-10T16:20:11.004Z",
"updatedAt": "2026-03-10T16:20:11.004Z",
"deletedAt": null
},
{
"id": "9d4f1b68-c2e7-4a05-93d1-7b6e0f2a8c34",
"organizationId": "7d3f8a52-1c0e-4b8e-9f4a-2e6b1d9c5a10",
"prescriptionId": "a7f35c20-9e18-4d64-8b3a-5c2d0e91f6b8",
"prescriptionTypeId": "62c8a0f4-3d19-4e7b-a5c2-f0b9d14e8a63",
"unitId": "b7e05a92-4f3c-4d18-86a1-2c9e7d3b5f40",
"order": 1,
"isRange": false,
"isTopMode": false,
"createdAt": "2026-03-10T16:20:11.004Z",
"updatedAt": "2026-03-10T16:20:11.004Z",
"deletedAt": null
}
],
"series": [
{
"id": "68c2e5b1-0d47-4a93-9f6e-b13a7d84c250",
"organizationId": "7d3f8a52-1c0e-4b8e-9f4a-2e6b1d9c5a10",
"prescriptionId": "a7f35c20-9e18-4d64-8b3a-5c2d0e91f6b8",
"order": 0,
"completedAt": "2026-03-17T10:06:30.512Z",
"createdAt": "2026-03-10T16:20:11.004Z",
"updatedAt": "2026-03-17T10:06:30.512Z",
"deletedAt": null,
"serieValues": [
{
"id": "0f6d2b84-a7e1-4c39-b05d-e9a3c8f1724b",
"organizationId": "7d3f8a52-1c0e-4b8e-9f4a-2e6b1d9c5a10",
"serieId": "68c2e5b1-0d47-4a93-9f6e-b13a7d84c250",
"seriePrescriptionId": "e3b72d05-6a1c-4f98-b4e3-1d8c9a5f0726",
"valuePrescriptedInf": null,
"valuePrescriptedSup": null,
"valuePrescripted": 100,
"valueExecuted": 100,
"createdAt": "2026-03-10T16:20:11.004Z",
"updatedAt": "2026-03-17T10:06:30.512Z",
"deletedAt": null
},
{
"id": "c5a19e37-04bd-4f62-8e7a-3b2d6f0c9a18",
"organizationId": "7d3f8a52-1c0e-4b8e-9f4a-2e6b1d9c5a10",
"serieId": "68c2e5b1-0d47-4a93-9f6e-b13a7d84c250",
"seriePrescriptionId": "9d4f1b68-c2e7-4a05-93d1-7b6e0f2a8c34",
"valuePrescriptedInf": null,
"valuePrescriptedSup": null,
"valuePrescripted": 5,
"valueExecuted": 5,
"createdAt": "2026-03-10T16:20:11.004Z",
"updatedAt": "2026-03-17T10:06:30.512Z",
"deletedAt": null
}
],
"metricValues": [
{
"id": "71e4c0a9-5d36-4b82-a1f7-9c8e2b4d0563",
"organizationId": "7d3f8a52-1c0e-4b8e-9f4a-2e6b1d9c5a10",
"metricId": "d06b3f81-4e92-4a7c-8c15-b7e29a0d5f63",
"value": 0.61,
"userId": "3b9c1e24-6f7a-4d2b-8c15-a40e9d7f1b62",
"exerciseId": "1c7a5e93-f804-4b26-a9d1-3e6b0d82c4f7",
"repetitionId": null,
"serieId": "68c2e5b1-0d47-4a93-9f6e-b13a7d84c250",
"createdAt": "2026-03-17T10:06:30.512Z",
"updatedAt": "2026-03-17T10:06:30.512Z",
"deletedAt": null
}
],
"repetitions": [
{
"id": "e19a4d83-5b60-4c27-a8f1-0d7c3e52b946",
"serieId": "68c2e5b1-0d47-4a93-9f6e-b13a7d84c250",
"organizationId": "7d3f8a52-1c0e-4b8e-9f4a-2e6b1d9c5a10",
"type": "concentric",
"repNumber": 1,
"repIndex": 0,
"createdAt": "2026-03-17T10:05:58.140Z",
"updatedAt": "2026-03-17T10:05:58.140Z",
"deletedAt": null,
"metricValues": [
{
"id": "86a1c4e7-3b05-4d9f-92e8-5f1d7c0a3b94",
"organizationId": "7d3f8a52-1c0e-4b8e-9f4a-2e6b1d9c5a10",
"metricId": "d06b3f81-4e92-4a7c-8c15-b7e29a0d5f63",
"value": 0.64,
"userId": "3b9c1e24-6f7a-4d2b-8c15-a40e9d7f1b62",
"exerciseId": "1c7a5e93-f804-4b26-a9d1-3e6b0d82c4f7",
"repetitionId": "e19a4d83-5b60-4c27-a8f1-0d7c3e52b946",
"serieId": "68c2e5b1-0d47-4a93-9f6e-b13a7d84c250",
"createdAt": "2026-03-17T10:05:58.140Z",
"updatedAt": "2026-03-17T10:05:58.140Z",
"deletedAt": null
},
{
"id": "3c7e9b20-a5f1-4d86-b2c4-71d0e8f3a659",
"organizationId": "7d3f8a52-1c0e-4b8e-9f4a-2e6b1d9c5a10",
"metricId": "2a9c6e41-b0d8-4f73-9e15-84d3c7a0b2f6",
"value": 1.12,
"userId": "3b9c1e24-6f7a-4d2b-8c15-a40e9d7f1b62",
"exerciseId": "1c7a5e93-f804-4b26-a9d1-3e6b0d82c4f7",
"repetitionId": "e19a4d83-5b60-4c27-a8f1-0d7c3e52b946",
"serieId": "68c2e5b1-0d47-4a93-9f6e-b13a7d84c250",
"createdAt": "2026-03-17T10:05:58.140Z",
"updatedAt": "2026-03-17T10:05:58.140Z",
"deletedAt": null
}
]
},
{
"id": "48b3f6d1-e2a0-4c95-b7d8-06f1a9c3e5b2",
"serieId": "68c2e5b1-0d47-4a93-9f6e-b13a7d84c250",
"organizationId": "7d3f8a52-1c0e-4b8e-9f4a-2e6b1d9c5a10",
"type": "concentric",
"repNumber": 2,
"repIndex": 1,
"createdAt": "2026-03-17T10:06:03.377Z",
"updatedAt": "2026-03-17T10:06:03.377Z",
"deletedAt": null,
"metricValues": [
{
"id": "d9c2a5e0-8b17-4f4d-a36e-5e0b7c1f9d84",
"organizationId": "7d3f8a52-1c0e-4b8e-9f4a-2e6b1d9c5a10",
"metricId": "d06b3f81-4e92-4a7c-8c15-b7e29a0d5f63",
"value": 0.62,
"userId": "3b9c1e24-6f7a-4d2b-8c15-a40e9d7f1b62",
"exerciseId": "1c7a5e93-f804-4b26-a9d1-3e6b0d82c4f7",
"repetitionId": "48b3f6d1-e2a0-4c95-b7d8-06f1a9c3e5b2",
"serieId": "68c2e5b1-0d47-4a93-9f6e-b13a7d84c250",
"createdAt": "2026-03-17T10:06:03.377Z",
"updatedAt": "2026-03-17T10:06:03.377Z",
"deletedAt": null
}
]
}
]
}
]
}
]
}
]
}
]
}
}Path parameters
iduuidrequired- Resource id.
Notes
- Every level is ordered by
order, thenid; repetitions byrepIndex, thenid. Deleted rows are never returned. serieValuesare returned in theunitIdof their serie prescription, as the app shows them (for example lb when the parameter is in lb).metricValues[].valueis in the metric’s own unit, seeGET /metrics/{id}. Repetition metrics appear only underrepetitions[], never in the set’smetricValues.- Prescriptions written before
trainingTypeexisted carrytrainingType: nullandisVbt: truefor VBT. - A workout older than the history your plan includes answers
404. - Pending workouts have
completedAt: nulland usually novalueExecuted, metrics or repetitions yet. - Raw encoder data is not exposed.
Response fields
namestring- Workout name.
userIduuid- Athlete the workout belongs to. Resolve with
GET /athletes/{id}. completedAtdatetimenullable- When the athlete finished the workout.
nullfor pending workouts. startedAtdatetimenullable- When the athlete started the workout.
rpeintegernullable- Session RPE entered by the athlete.
scheduledWorkoutIduuidnullable- Program slot this workout is attached to (
phases[].scheduledWorkouts[].idinGET /programs/{id}). Unique: one workout per slot. testTypestringnullable- Set when the workout is a standardized test (for example
vertical_jump,t_test), otherwisenull. createdAtdatetime- When the workout was created or assigned.
blocks[]object[]- Blocks, ordered by
order. blocks[].prescriptionsGroups[]object[]- Groups, ordered by
order. A group with more than one prescription is a superset. blocks[].prescriptionsGroups[].prescriptions[]object[]- One exercise each, ordered by
order. …prescriptions[].exerciseIduuid- Exercise performed. Resolve with
GET /exercises/{id}. …prescriptions[].trainingTypestringnullablevbt,jump,sprintorreaction.nullon rows written before the field existed; readisVbtthen.…prescriptions[].trainingAlgorithmstringnullable- Variant within the training type, for example
10-5for jump orgo-no-gofor reaction. …prescriptions[].notestringnullable- Coach note for the exercise.
…prescriptions[].seriePrescriptions[]object[]- The prescribed parameters (load, reps, velocity target…), ordered by
order. …seriePrescriptions[].prescriptionTypeIduuid- What the parameter is. Resolve with
GET /prescription-types/{id}. …seriePrescriptions[].unitIduuid- Unit the parameter’s
serieValuesare expressed in. Resolve withGET /units/{id}. …seriePrescriptions[].isRangebooleannullable- When
true, the target isvaluePrescriptedInf–valuePrescriptedSupinstead ofvaluePrescripted. …prescriptions[].series[]object[]- Sets, ordered by
order. …series[].completedAtdatetimenullable- When the set was completed.
…series[].serieValues[]object[]- One value per serie prescription, linked by
seriePrescriptionId. …serieValues[].valuePrescriptednumbernullable- Prescribed value, in the
unitIdof its serie prescription. …serieValues[].valuePrescriptedInfnumbernullable- Lower bound of a prescribed range, same unit.
…serieValues[].valuePrescriptedSupnumbernullable- Upper bound of a prescribed range, same unit.
…serieValues[].valueExecutednumbernullable- Value actually performed, same unit.
nulluntil the set is done. …series[].metricValues[]object[]- Metrics captured for the whole set (
repetitionId: null). …metricValues[].metricIduuid- Metric measured. Resolve with
GET /metrics/{id}. …metricValues[].valuenumber- Measured value, in the metric’s
unitId. …series[].repetitions[]object[]- Repetitions, ordered by
repIndex. …repetitions[].repNumberinteger- Repetition number as shown in the app.
…repetitions[].repIndexinteger- Zero-based position of the repetition in the set.
…repetitions[].typestringnullableconcentricoreccentric.nullon older rows: read it as “not eccentric”.…repetitions[].metricValues[]object[]- Metrics captured for this repetition.
Failures are described in error codes.