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 / filterMax use the stored unit of filterMetricId too. 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: from and to are required and inclusive, and bare dates are read in timezone. The response returns the range read.
  • Not paginated. A trend reads at most 50 athletes; a ranking returns up to limit of them.
  • Refused and failed queries differ. A query the engine refuses, such as a calculation the metric doesn't support, answers 400 with the reason. One that fails while running, because the engine is busy or the query ran too long, answers a generic 503: retry later or narrow the range.

Get a metric trend

GET/analytics/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"

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 its unitId.
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: sum only applies to additive metrics, body metrics take latest, avg, max or min, and categorical metrics only latest. Any other combination answers 400.
fromdaterequired
Inclusive lower bound: YYYY-MM-DD or an ISO 8601 datetime. A bare date starts at 00:00 in timezone, or at the change on a day whose midnight a daylight-saving change skips.
todaterequired
Inclusive upper bound: YYYY-MM-DD or an ISO 8601 datetime. A bare date covers that whole day in timezone.
timezonestringdefault UTC
IANA time zone, such as Europe/Madrid. A bare from / to date covers that whole day in it, and a datetime without an offset is a wall-clock time in it. Datetimes with Z or 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 unless all.
filterMetricIduuid
Companion metric to filter on, such as the load. Only values whose companion value is within filterMin / filterMax are 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: day for ranges up to 93 days, week up to 731, month for any range. Defaults to the one the range implies; a range longer than the granularity covers answers 400.

Notes

  • A trend reads at most 50 athletes. More answers 400: narrow it with userIds or teamId.
  • 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 400 with the reason. One that fails while running answers a generic 5xx: retry later or narrow the range.

Response fields

metricIduuid
Metric aggregated.
unitIduuid
Unit of every value: the metric’s stored unit. Resolve with GET /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 (concentric or eccentric). Absent when every phase or a metric without phases was read.
athleteCountinteger
Athletes the query covered, with or without data.
granularitystring
Bucket size: the granularity asked for, else the one the range implies (day up to 31 days, week up to 93, month beyond).
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

GET/analytics/ranking

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"

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 its unitId.
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: sum only applies to additive metrics, body metrics take latest, avg, max or min, and categorical metrics only latest. Any other combination answers 400.
fromdaterequired
Inclusive lower bound: YYYY-MM-DD or an ISO 8601 datetime. A bare date starts at 00:00 in timezone, or at the change on a day whose midnight a daylight-saving change skips.
todaterequired
Inclusive upper bound: YYYY-MM-DD or an ISO 8601 datetime. A bare date covers that whole day in timezone.
timezonestringdefault UTC
IANA time zone, such as Europe/Madrid. A bare from / to date covers that whole day in it, and a datetime without an offset is a wall-clock time in it. Datetimes with Z or 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 unless all.
filterMetricIduuid
Companion metric to filter on, such as the load. Only values whose companion value is within filterMin / filterMax are 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 | ascdefault desc
desc puts the highest value first, asc the lowest. Ties are ordered by userId.
limitintegerdefault 25
Athletes returned, 1–100.

Notes

  • To rank a leaderboard, pass its metricId, exerciseId and 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 400 with the reason. One that fails while running answers a generic 5xx: retry later or narrow the range.

Response fields

metricIduuid
Metric aggregated.
unitIduuid
Unit of every value: the metric’s stored unit. Resolve with GET /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 (concentric or eccentric). 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. athletes holds the first limit of 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.

On this page