Analytics
Metric trends and athlete rankings, aggregated by the engine behind Vitruve Labs.
These endpoints aggregate one metric across athletes, so you do not have to walk every workout tree and compute it yourself. They read the same engine as Vitruve Labs, with the same defaults: repetition metrics read the concentric phase unless you pass repetitionType.
- Values are in the metric's stored unit (
unitId, for example m/s or kg), not the organization's display units.filterMin/filterMaxuse the stored unit offilterMetricIdtoo. Resolve units with the catalogs. - Per-exercise metrics need
exerciseId, such as velocity or power on a back squat. Metrics that are not per exercise, such as body weight, reject it. - Dates follow the filtering conventions:
fromandtoare required and inclusive, and bare dates are read intimezone. The response returns the range read. - Not paginated. A trend reads at most 50 athletes; a ranking returns up to
limitof them. - Refused and failed queries differ. A query the engine refuses, such as a calculation the metric doesn't support, answers
400with the reason. One that fails while running, because the engine is busy or the query ran too long, answers a generic503: retry later or narrow the range.
Get a metric trend
One metric aggregated per athlete and period over a date range: the series Vitruve Labs charts. Not paginated.
Request
curl "https://api-next.vitruve.fit/v1/analytics/trend?metricId=d06b3f81-4e92-4a7c-8c15-b7e29a0d5f63&calculation=max&exerciseId=1c7a5e93-f804-4b26-a9d1-3e6b0d82c4f7&from=2025-12-01&to=2026-03-31&timezone=Europe%2FMadrid&teamId=9a4f6d13-8b25-4c7e-93a0-1f5d2e8b7c64" \
-H "X-API-Key: YOUR_API_KEY"const response = await fetch(
'https://api-next.vitruve.fit/v1/analytics/trend?metricId=d06b3f81-4e92-4a7c-8c15-b7e29a0d5f63&calculation=max&exerciseId=1c7a5e93-f804-4b26-a9d1-3e6b0d82c4f7&from=2025-12-01&to=2026-03-31&timezone=Europe%2FMadrid&teamId=9a4f6d13-8b25-4c7e-93a0-1f5d2e8b7c64',
{ headers: { 'X-API-Key': process.env.VITRUVE_API_KEY } }
)
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-next.vitruve.fit/v1/analytics/trend?metricId=d06b3f81-4e92-4a7c-8c15-b7e29a0d5f63&calculation=max&exerciseId=1c7a5e93-f804-4b26-a9d1-3e6b0d82c4f7&from=2025-12-01&to=2026-03-31&timezone=Europe%2FMadrid&teamId=9a4f6d13-8b25-4c7e-93a0-1f5d2e8b7c64",
headers={"X-API-Key": os.environ["VITRUVE_API_KEY"]},
timeout=30,
)
response.raise_for_status()
print(response.json()["data"])Response 200
{
"ok": true,
"timestamp": 1738247125207,
"data": {
"metricId": "d06b3f81-4e92-4a7c-8c15-b7e29a0d5f63",
"unitId": "fa52c8d4-17e3-4b69-8d0a-6c3f9e1b2a78",
"calculation": "max",
"granularity": "month",
"from": "2025-11-30T23:00:00.000Z",
"to": "2026-03-31T21:59:59.999Z",
"repetitionType": "concentric",
"athleteCount": 12,
"athletes": [
{
"userId": "3b9c1e24-6f7a-4d2b-8c15-a40e9d7f1b62",
"buckets": [
{
"bucketStart": "2025-11-30T23:00:00.000Z",
"value": 0.71
},
{
"bucketStart": "2025-12-31T23:00:00.000Z",
"value": 0.74
},
{
"bucketStart": "2026-01-31T23:00:00.000Z",
"value": 0.78
},
{
"bucketStart": "2026-02-28T23:00:00.000Z",
"value": 0.81
}
]
},
{
"userId": "c81d5f07-92ab-4e63-b0d4-7a3e6c1f9e28",
"buckets": [
{
"bucketStart": "2025-12-31T23:00:00.000Z",
"value": 0.69
},
{
"bucketStart": "2026-02-28T23:00:00.000Z",
"value": 0.72
}
]
}
]
}
}Query parameters
metricIduuidrequired- Metric to aggregate. Resolve with
GET /metrics/{id}, which also gives itsunitId. calculationavg | max | min | sum | median | latestrequired- How each athlete’s values are combined: average, maximum, minimum, total, median, or the most recent value. Not every metric takes every calculation:
sumonly applies to additive metrics, body metrics takelatest,avg,maxormin, and categorical metrics onlylatest. Any other combination answers400. fromdaterequired- 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. todaterequired- 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. It also sets where days, weeks and months start. userIdsstring- Comma-separated athlete ids, at most 100. Defaults to every athlete of your organization you can see.
teamIduuid- Only athletes in this team.
exerciseIduuid- Exercise the values were captured on. Required for per-exercise metrics (velocity, power, load…) and rejected for the others (body weight…).
repetitionTypeconcentric | eccentric | all- Repetition phase to read. Repetition metrics default to
concentric; rejected for metrics without phases unlessall. filterMetricIduuid- Companion metric to filter on, such as the load. Only values whose companion value is within
filterMin/filterMaxare kept. Needs at least one of the bounds. filterMinnumber- Inclusive lower bound on
filterMetricId, in that metric’s stored unit (kg for load). filterMaxnumber- Inclusive upper bound on
filterMetricId, in that metric’s stored unit. granularityday | week | month- Bucket size:
dayfor ranges up to 93 days,weekup to 731,monthfor any range. Defaults to the one the range implies; a range longer than the granularity covers answers400.
Notes
- A trend reads at most 50 athletes. More answers 400: narrow it with
userIdsorteamId. - Values are in the metric’s stored unit (
unitId), never converted to the organization’s display units. - Athletes with no value in the range are left out of
athletes. - A query the engine refuses, such as a calculation the metric doesn’t support, answers
400with the reason. One that fails while running answers a generic5xx: retry later or narrow the range.
Response fields
metricIduuid- Metric aggregated.
unitIduuid- Unit of every
value: the metric’s stored unit. Resolve withGET /units/{id}. calculationstring- Calculation applied.
fromdatetime- Start of the range actually read, after your plan’s history window.
todatetime- End of the range read.
repetitionTypestringnullable- Repetition phase read (
concentricoreccentric). Absent when every phase or a metric without phases was read. athleteCountinteger- Athletes the query covered, with or without data.
granularitystring- Bucket size: the
granularityasked for, else the one the range implies (dayup to 31 days,weekup to 93,monthbeyond). athletes[].userIduuid- Athlete. Resolve with
GET /athletes/{id}. athletes[].buckets[]object[]- Periods with a value, oldest first. Periods without data are left out.
athletes[].buckets[].bucketStartdatetime- Start of the period in
timezone: the day, the week (starting Sunday) or the month. athletes[].buckets[].valuenumber- The calculation over the period, in
unitId.
Failures are described in error codes.
Rank athletes on a metric
One metric aggregated per athlete over a date range, sorted into a ranking. Not paginated.
Request
curl "https://api-next.vitruve.fit/v1/analytics/ranking?metricId=d06b3f81-4e92-4a7c-8c15-b7e29a0d5f63&calculation=max&exerciseId=1c7a5e93-f804-4b26-a9d1-3e6b0d82c4f7&from=2026-03-01&to=2026-03-31&filterMetricId=6e0c2b94-7a1f-4d58-b3e6-0f9d8a21c7b5&filterMin=100&limit=10" \
-H "X-API-Key: YOUR_API_KEY"const response = await fetch(
'https://api-next.vitruve.fit/v1/analytics/ranking?metricId=d06b3f81-4e92-4a7c-8c15-b7e29a0d5f63&calculation=max&exerciseId=1c7a5e93-f804-4b26-a9d1-3e6b0d82c4f7&from=2026-03-01&to=2026-03-31&filterMetricId=6e0c2b94-7a1f-4d58-b3e6-0f9d8a21c7b5&filterMin=100&limit=10',
{ headers: { 'X-API-Key': process.env.VITRUVE_API_KEY } }
)
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-next.vitruve.fit/v1/analytics/ranking?metricId=d06b3f81-4e92-4a7c-8c15-b7e29a0d5f63&calculation=max&exerciseId=1c7a5e93-f804-4b26-a9d1-3e6b0d82c4f7&from=2026-03-01&to=2026-03-31&filterMetricId=6e0c2b94-7a1f-4d58-b3e6-0f9d8a21c7b5&filterMin=100&limit=10",
headers={"X-API-Key": os.environ["VITRUVE_API_KEY"]},
timeout=30,
)
response.raise_for_status()
print(response.json()["data"])Response 200
{
"ok": true,
"timestamp": 1738247125207,
"data": {
"metricId": "d06b3f81-4e92-4a7c-8c15-b7e29a0d5f63",
"unitId": "fa52c8d4-17e3-4b69-8d0a-6c3f9e1b2a78",
"calculation": "max",
"order": "desc",
"from": "2026-03-01T00:00:00.000Z",
"to": "2026-03-31T23:59:59.999Z",
"repetitionType": "concentric",
"athleteCount": 24,
"rankedCount": 2,
"athletes": [
{
"rank": 1,
"userId": "3b9c1e24-6f7a-4d2b-8c15-a40e9d7f1b62",
"value": 0.71,
"date": "2026-03-18T10:21:47.302Z"
},
{
"rank": 2,
"userId": "c81d5f07-92ab-4e63-b0d4-7a3e6c1f9e28",
"value": 0.64,
"date": "2026-03-11T17:05:12.880Z"
}
]
}
}Query parameters
metricIduuidrequired- Metric to aggregate. Resolve with
GET /metrics/{id}, which also gives itsunitId. calculationavg | max | min | sum | median | latestrequired- How each athlete’s values are combined: average, maximum, minimum, total, median, or the most recent value. Not every metric takes every calculation:
sumonly applies to additive metrics, body metrics takelatest,avg,maxormin, and categorical metrics onlylatest. Any other combination answers400. fromdaterequired- 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. todaterequired- 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. It also sets where days, weeks and months start. userIdsstring- Comma-separated athlete ids, at most 100. Defaults to every athlete of your organization you can see.
teamIduuid- Only athletes in this team.
exerciseIduuid- Exercise the values were captured on. Required for per-exercise metrics (velocity, power, load…) and rejected for the others (body weight…).
repetitionTypeconcentric | eccentric | all- Repetition phase to read. Repetition metrics default to
concentric; rejected for metrics without phases unlessall. filterMetricIduuid- Companion metric to filter on, such as the load. Only values whose companion value is within
filterMin/filterMaxare kept. Needs at least one of the bounds. filterMinnumber- Inclusive lower bound on
filterMetricId, in that metric’s stored unit (kg for load). filterMaxnumber- Inclusive upper bound on
filterMetricId, in that metric’s stored unit. orderdesc | ascdefaultdescdescputs the highest value first,ascthe lowest. Ties are ordered byuserId.limitintegerdefault25- Athletes returned, 1–100.
Notes
- To rank a leaderboard, pass its
metricId,exerciseIdand athletes (userIds). - Values are in the metric’s stored unit (
unitId), never converted to the organization’s display units. - Athletes with no value in the range are left out of
athletes. - A query the engine refuses, such as a calculation the metric doesn’t support, answers
400with the reason. One that fails while running answers a generic5xx: retry later or narrow the range.
Response fields
metricIduuid- Metric aggregated.
unitIduuid- Unit of every
value: the metric’s stored unit. Resolve withGET /units/{id}. calculationstring- Calculation applied.
fromdatetime- Start of the range actually read, after your plan’s history window.
todatetime- End of the range read.
repetitionTypestringnullable- Repetition phase read (
concentricoreccentric). Absent when every phase or a metric without phases was read. athleteCountinteger- Athletes the query covered, with or without data.
orderstring- Sort order applied.
rankedCountinteger- Athletes with a value, before
limit.athletesholds the firstlimitof them. athletes[].rankinteger- Position, starting at 1.
athletes[].userIduuid- Athlete. Resolve with
GET /athletes/{id}. athletes[].valuenumber- The calculation over the range, in
unitId. athletes[].datedatetimenullable- When the value was recorded, for calculations that pick one value (
max,min,latest). Absent otherwise.
Failures are described in error codes.
Measurements
Load-velocity profiles, leaderboards, wellness questionnaires and injury reports. Load-velocity profiles and injury reports can also be written.
Catalogs
Exercises, muscles, metrics, prescription types and units: the reference data that ids in other resources point to. Your organization's own exercises can also be created, updated and deleted.