Vekta API

Workout structure

How to describe the steps of a workout, and what Vekta does with them.

A training prescription or a training collection can have a structure: the list of steps of the workout, with their targets. Vekta uses it to estimate the workout (duration, distance, work...) and to send it to the athlete's devices.

Structures are only available for cycling, running and swimming.

Steps and repetitions

A structure is a list of steps and repetitions.

A step has:

  • a label: warm up, active, intra-recovery, rest, cool down or maximum;
  • a length: a duration in seconds, a distance in meters, or free when the athlete decides when to move on;
  • an intensity: its targets, all optional.

A repetition is a block of steps done nRepetitions times. It can contain other repetitions, up to 3 levels deep.

For example, 3 × 12 minutes at threshold in a cycling workout with zonesType: "power" (explained below):

[
  { "type": "step", "label": "warm up", "length": { "type": "duration", "duration": 900 } },
  {
    "type": "repetition",
    "nRepetitions": 3,
    "steps": [
      {
        "type": "step",
        "label": "active",
        "length": { "type": "duration", "duration": 720 },
        "intensity": { "power": { "zone": "threshold" }, "cadence": { "min": 85, "max": 95 } }
      },
      {
        "type": "step",
        "label": "intra-recovery",
        "length": { "type": "duration", "duration": 300 },
        "intensity": { "power": { "minPercentage": 50, "maxPercentage": 60 } }
      }
    ]
  },
  { "type": "step", "label": "cool down", "length": { "type": "free" } }
]

Targets

Each sport accepts different targets:

TargetCyclingRunningSwimmingUnit
power✓✓watts
heartRate✓✓✓bpm
speed✓✓m/s
cadence✓✓rpm (steps per minute for running)
stroke✓freestyle, backstroke...
passive✓true for a rest at the wall

Sending a target the sport doesn't accept returns a 400.

power, heartRate and speed can be given in three ways:

  • A zone: { "zone": "threshold" }, the name of one of the athlete's zones. Only for the zonesType target (see below), and not in collections: a collection isn't tied to an athlete, so it has no zones.
  • A % of a metric: { "minPercentage": 95, "maxPercentage": 105 }. The metric is the critical power for power, the max heart rate for heart rate, and the critical speed for speed.
  • An absolute range: { "min": 250, "max": 280 }, in the target's unit.

The main target: zonesType

Whenever you send a structure, also send a zonesType in the same request. It has to be one the sport allows:

SportAllowed zonesType
Cyclingpower, heartRate
Runningspeed, heartRate, power
Swimmingspeed, heartRate

Only the zonesType target can be given as a zone. With zonesType: "power", a step can have { "power": { "zone": "threshold" } }, but its heart rate can only be a % or an absolute range.

Why a single kind of zone? Vekta tells you the zone of each step (the step's zone, in what it returns). Athletes have power, heart rate and speed zones, often with the same names, so a step's threshold could mean three different things. With one zonesType, every zone in the workout is of that kind: the step's zone is always the one its zonesType target falls in.

Why collections need one too. A collection has no zones, but when you prescribe it, Vekta finds the zone of each step for that athlete. The zonesType tells it which target to use, so again only one kind of zone shows up.

How targets are converted

When you create a prescription with a structure, send a new one, or prescribe a collection, Vekta converts every target with the athlete's zones and latest metrics. A collection's targets are kept as you send them until then.

  • A zone gives the range of that zone. The athlete's zones are the ones set for the sport and zonesType. If they have none, Vekta's default zones are computed from their metric (for example their critical power).
  • A % gives a range in the target's unit, from the athlete's metric.
  • An absolute range also gets its % of the metric, when the athlete has it.

If a step needs something the athlete doesn't have, you get a 400, for example: a % of power without a critical power, a zone that isn't one of the athlete's zones, or a zone with neither zones nor a metric to compute them from.

What Vekta computes

From the steps, Vekta estimates:

  • duration: the sum of the step durations. Steps with a distance use their speed target to get a duration.
  • distance: the sum of the step distances. Steps with a duration use their speed target to get a distance. Rests at the wall count as 0.
  • work, averagePower and calories: from the power of each step. Steps without power count as 0 W.
  • carbIntake: only if you don't send it, when the athlete has the metrics it needs, unless the workspace the prescription was created in has carb recommendations turned off.

When Vekta can estimate a value, it replaces the one you send. When it can't (for example a distance step without a speed target):

  • The value you send is used, null included.
  • If you don't send one and the steps stay the same, the previous value stays. Prescribing a collection with its own steps keeps the collection's values.
  • If you don't send one with new steps, it's cleared, since it belonged to the old steps. Send it again if you want to keep it.

For a collection, only duration and distance are estimated. Since there's no athlete, only absolute speed targets can turn a distance into a duration, or the other way around.

Free steps

Steps with a free length share the time the other steps leave. If you send "duration": 3600 and the other steps add up to 50 minutes, the remaining 10 minutes are split equally between the free steps. You get each one's share back in freeDuration.

This time is used in two ways:

  • For the estimates: with it, the free steps count in the work, average power and calories.
  • For devices: Wahoo, Zwift and Hammerhead don't support free steps, so they get each free step as a step of freeDuration. Without a freeDuration (no duration, or one shorter than the other steps), the workout can't be sent to them, and deviceSyncs says so.

When you send new steps with free steps, send the duration too: as for the other values, the previous one isn't reused.

Updating

Without a new structure, the stored one is kept. You can still change duration, distance and work, except when the steps decide them:

  • duration, when every step has a duration;
  • distance, when every step has a distance (rests at the wall aside);
  • work, when every step has a power and a duration.

Sending a different value in these cases returns a 400. Changing the duration of a structure with free steps shares the new time between them.

To change the sport of a workout with steps, also send a new structure, or structure: null to remove the steps. The old steps may not fit the new sport.

structure: null removes the steps. The workout keeps its other fields.

The steps aren't converted again when the athlete's zones or metrics change. They're converted with their current values when you send the structure again, or when you change the duration of a structure with free steps (which fails with a 400 if a step now needs a zone or metric the athlete doesn't have).

Changing a collection never changes the prescriptions already made from it.

Devices

Unless it's hidden, a prescription with steps is sent to the athlete's connected devices. Updating or deleting it updates or removes it there too. The deviceSyncs field of the response tells you what happened on each device, and why when the workout couldn't be sent. A prescription without steps isn't sent to devices.

Each device supports different things. A workout a device can't take is skipped on it (for example a past date on Zwift), and deviceSyncs says why.

GarminCOROSWahooZwiftHammerhead
Cycling✓✓✓✓✓
Running✓✓✓✓
Swimming✓✓
Steps with a distance✓✓✓
Free steps✓✓with freeDurationwith freeDurationwith freeDuration
Targets per stepup to 2 (1 for running)1all1 (+ cadence)1 (+ cadence)
Datesanyfrom today, up to 1 year aheadanyfrom todayany

How each device picks the targets of a step:

  • Garmin: the zonesType target first, then the other targets, then cadence, keeping the first two the step has (only one for running). For swimming, the speed target and the stroke.
  • COROS: one target: the zonesType one, or else cadence for running, or else the other target the step has. For swimming, only the stroke.
  • Wahoo: every target the step has: power, heart rate, speed and cadence.
  • Zwift: power for cycling and speed for running, as a % of the athlete's critical power or critical speed, plus cadence. Every step needs one, so the athlete needs that metric.
  • Hammerhead: like Zwift, for cycling only.

Reading a structure back

The structure you get back has the same steps, repetitions, labels and lengths as the one you sent. Only each step's intensity is different: it lists every field of the sport, set to null when unused (except passive, only present on rests at the wall), with the values Vekta converted. For example, the first active step above, for an athlete with a critical power of 280 W:

{
  "vektaIntensity": "threshold",
  "zone": "threshold",
  "fromPower": "zone",
  "minPower": 252,
  "maxPower": 294,
  "minPowerThresholdPercentage": 90,
  "maxPowerThresholdPercentage": 105,
  "fromHeartRate": null,
  "minHeartRate": null,
  "maxHeartRate": null,
  "minMaxHeartRatePercentage": null,
  "maxMaxHeartRatePercentage": null,
  "minCadence": 85,
  "maxCadence": 95
}
  • from… tells how the target was given: zone, metric (a %) or absolute (a range in the target's unit).
  • min…/max… are in the target's unit, and the …Percentage fields are the same range in % of the metric.
  • zone is the athlete's zone the step is in, for the zonesType target.
  • vektaIntensity is Vekta's own classification of the step.
  • Swimming steps have no cadence, but have stroke (null when not set), and passive: true for rests at the wall.
  • In a collection, nothing is converted yet: a % target keeps its …Percentage fields, and its min…/max… stay null until it's prescribed.

This returned intensity format may change in the future, to look like the one you send ({ "power": { "zone": "threshold" } }...).

On this page