Workout Schema
Laatst bijgewerkt: 2026-05-29
Your Trainer is een indoor-cycling-app voor meerdere rijders op Android-tablets. Smart-trainer-besturing met lokale data + lokale controle. Eenmalige aankoop.
Het .ytw workoutbestand is platte JSON — schrijf het in een willekeurige teksteditor, importeer via het deelmenu, deel met vrienden. De visuele editor dekt veelvoorkomende gevallen; het schema is de uitweg wanneer je volledige controle nodig hebt.
Wanneer handmatig schrijven
De meeste fietsers hebben deze pagina nooit nodig — de visuele workout-editor en de AI Workout Coach dekken alles van een 4×8 drempelsessie tot een microburst-stack. Grijp naar het schema wanneer:
- Je een structuur wilt die de editor niet aanbiedt (zeer lange warming-ups, uitgebreide cue-scripting, gemengde cadanstargets per interval).
- Je een workout vertaalt die je al hebt in het formaat van een andere tool.
- Je een workout als één bestand naar iemand anders wilt sturen.
- Je in bulk programmatisch workouts wilt genereren.
Minimaal voorbeeld
Het kortste geldige .ytw-bestand is één programma met één interval. Sla op met een .ytw-extensie en deel naar Your Trainer.
{
"programId": "my-sweet-spot",
"programName": "My Sweet Spot 30",
"description": "A short sweet-spot workout.",
"totalDuration": 1800,
"workoutType": "POWER",
"primaryLocale": "en",
"intervals": [
{ "id": "warmup", "duration": 300, "targetPowerPercent": 50, "intensityZone": "Z1", "label": "Warmup", "intervalType": "WARMUP" },
{ "id": "work", "duration": 1200, "targetPowerPercent": 88, "intensityZone": "Z3", "label": "Sweet Spot", "intervalType": "INTERVAL" },
{ "id": "cooldown", "duration": 300, "targetPowerPercent": 50, "intensityZone": "Z1", "label": "Cooldown", "intervalType": "COOLDOWN" }
]
}
Velden op hoofdniveau
Het object op hoofdniveau beschrijft één workoutprogramma. Vereiste velden zijn gemarkeerd.
| Veld | Type | Beschrijving |
|---|---|---|
programId vereist | string | Stabiele identifier. Gebruik kebab-case (my-sweet-spot). De unieke sleutel voor de workout in je bibliotheek — het opnieuw importeren van een bestand met dezelfde programId werkt de bestaande entry bij in plaats van een duplicaat aan te maken. |
programName vereist | string | Weergavenaam in de primaire locale van de fietser. |
description vereist | string | Beschrijving van één of twee zinnen die op de workoutkaart wordt getoond. |
totalDuration vereist | integer (seconden) | Totale workoutlengte. De app berekent dit opnieuw uit de intervallen bij het opslaan, dus het is veilig om dit inconsistent te laten tijdens het schrijven. |
intervals vereist | array | Geordende lijst van intervalobjecten of herhalingsgroepen. |
workoutType | string | Workoutfamilie: POWER (standaard), HR_ZONE, of ROUTE. Zie Workouttypes. |
variant | string | Subvorm binnen de familie: STANDARD (standaard) of RAMP_TEST. |
primaryLocale | string (BCP-47) | Locale waarin de strings zijn geschreven. Standaard "en". Bepaalt de fallback-keten tussen locales. |
category | string | Vrije-tekst categorisatie (bijv. "threshold", "endurance"). Optioneel. |
difficulty | integer (1–5) | Subjectieve moeilijkheid. Verschijnt op de workoutkaart. |
isUserCreated | boolean | True voor workouts die in de app door de fietser zijn geschreven; false voor geïmporteerde / meegeleverde workouts. Standaard false. |
isFavorite | boolean | Vlag voor bovenaan vastgepind. Fietsers wisselen dit in de app; meestal weggelaten in gedeelde bestanden. |
routeProfile | array van { distanceMeters, elevationMeters } | Volledig hoogteprofiel voor ROUTE-workouts. Null voor POWER en HR_ZONE. |
strings | object (locale → LocaleStrings) | Vertalingen per locale van naam, beschrijving, intervallabels en cues. |
Intervalvelden
Elk intervalobject beschrijft één blok van de workout. De vorm van het blok hangt af van de workoutType van de ouder — Power-blokken gebruiken vermogenspercentages, HF-Zone-blokken gebruiken een zonetarget.
| Veld | Type | Beschrijving |
|---|---|---|
duration vereist | integer (seconden) | Bloklengte in seconden. |
targetPowerPercent power-vereist | integer (% van FTP) | Vermogenstarget voor POWER-workouts. Wederzijds uitsluitend met targetHrZone. |
targetPowerEndPercent | integer (% van FTP) | Optioneel ramp-eindvermogen. Wanneer aanwezig interpoleert het doelwattage lineair van targetPowerPercent → targetPowerEndPercent over het blok. |
targetHrZone hr-vereist | integer (1–5) | HF-zone voor HR_ZONE-workouts. Wederzijds uitsluitend met targetPowerPercent. |
intensityZone vereist | string | Visueel zonetoken: Z1–Z5. Bepaalt de kleur op de terreinvisualisatie. Zie Trainingszones. |
intervalType | string | WARMUP, COOLDOWN, of INTERVAL (standaard). Warming-up- en cooling-down-blokken worden uitgesloten van werk-only samenvattingen (gemiddeld vermogen van werkblokken, tijd-in-zone voor het werkgedeelte, enz.). |
label vereist | string | Weergavetekst op het blok in de primaire locale van de fietser. Varianten voor andere locales staan in strings.<locale>.labels. |
id | string | Stabiele slug — de sleutel die wordt gebruikt in strings.<locale>.labels en in de cue-key samenstelling. Aanbevolen voor elke workout die met vertalingen wordt geleverd. |
autoLabel | boolean | True wanneer het label is gegenereerd door een editor-preset in plaats van getypt door de fietser. Auto-labels worden door Your Trainer zelf gelokaliseerd en hebben geen entries per locale nodig in strings. Standaard false. |
cadenceTarget | integer (RPM) | Optionele cadanstarget voor het blok (bijv. 60 voor lage-cadans klimwerk, 100 voor spin-up oefeningen). |
cues | array van CoachingCue | Coaching-cues die tijdens het blok afgaan. |
Coaching-cues
Een coaching-cue is een korte tekstoverlay die tijdens een rit op de cockpit verschijnt. Elke cue heeft een offset binnen zijn parent-interval, de tekst om te tonen, en hoe lang deze op het scherm blijft.
| Veld | Type | Beschrijving |
|---|---|---|
offsetSec vereist | integer (seconden) | Seconden vanaf het begin van het parent-interval waarop de cue afgaat. |
text vereist | string | Cue-tekst in de primaire locale van de workout. Varianten voor andere locales staan in strings.<locale>.cues, gesleuteld op <intervalId>:<cueIndex>. |
durationSec | integer (seconden) | Hoe lang de cue op het scherm blijft. Standaard 5. |
Voorbeeld van een interval met drie cues (sleutelsamenstelling gebruikt de id van het parent-interval + de index van de cue in de array):
{
"id": "work",
"duration": 600,
"targetPowerPercent": 95,
"intensityZone": "Z4",
"label": "Threshold",
"intervalType": "INTERVAL",
"cues": [
{ "offsetSec": 0, "text": "Settle in — find your rhythm." },
{ "offsetSec": 300, "text": "Halfway. Stay smooth.", "durationSec": 8 },
{ "offsetSec": 540, "text": "One minute. Hold form." }
]
}
Gelokaliseerde strings & fallback-keten
Het strings-blok bevat per locale vertalingen van elke voor de fietser zichtbare string in de workout. Elke locale-entry heeft dezelfde vorm:
"strings": {
"en": {
"name": "Sweet Spot 30",
"description": "A short sweet-spot workout.",
"labels": { "warmup": "Warmup", "work": "Sweet Spot", "cooldown": "Cooldown" },
"cues": { "work:0": "Settle in", "work:1": "Halfway" }
},
"de": {
"name": "Sweet Spot 30",
"description": "Ein kurzes Sweet-Spot-Training.",
"labels": { "warmup": "Aufwärmen", "work": "Sweet Spot", "cooldown": "Ausrollen" },
"cues": { "work:0": "Locker einrollen", "work:1": "Halbzeit" }
}
}
Cue-sleutels volgen het patroon <intervalId>:<cueIndex> — dus de eerste cue op het work-interval heeft als sleutel work:0.
Voor elke voor de fietser zichtbare string kiest de app de beste locale-match in deze volgorde:
strings[<rider-locale>]— de eigen locale van de fietser.strings[primaryLocale]— de locale van de auteur.strings["en"]— universele fallback.- Het veld op hoofdniveau (
programName, interval-label, cue-text).
Strings die worden getoond vanuit een andere locale dan die van de fietser verschijnen cursief op workoutkaarten en de cockpit, zodat de fietser kan zien welke strings nog niet zijn vertaald.
strings blocks for additional locales when prompted.Herhalingsgroepen
Voor zich herhalende structuren schrijft een herhalingsgroep de eenheid één keer en vertelt de app hoe vaak deze moet worden afgespeeld. Herhalingsgroepen worden bij het importeren uitgevouwen tot individuele blokken, zodat de fietser elk blok ziet in de strook met komende intervallen tijdens de rit.
{
"intervals": [
{ "id": "warmup", "duration": 600, "targetPowerPercent": 50, "intensityZone": "Z1", "label": "Warmup", "intervalType": "WARMUP" },
{
"repeat": 4,
"intervals": [
{ "id": "on", "duration": 480, "targetPowerPercent": 95, "intensityZone": "Z4", "label": "Threshold" },
{ "id": "off", "duration": 240, "targetPowerPercent": 55, "intensityZone": "Z1", "label": "Recovery" }
]
},
{ "id": "cooldown", "duration": 600, "targetPowerPercent": 50, "intensityZone": "Z1", "label": "Cooldown", "intervalType": "COOLDOWN" }
]
}
Het bovenstaande voorbeeld vouwt uit naar 1 warming-up + 4×(drempel + herstel) + 1 cooling-down = 10 blokken. Herhalingsgroepen kunnen nesten, maar plat heeft de voorkeur voor leesbaarheid.
Workouttypes & varianten
Het workoutType-veld selecteert het paradigma; variant selecteert een subvorm daarbinnen.
workoutType | Wat het is | Vereiste intervalvorm |
|---|---|---|
POWER (standaard) | FTP-verankerde intervallen. De trainer rijdt het doelwattage in ERG, of volgt weerstandscurves in SIM. | Elk interval heeft targetPowerPercent (en optioneel targetPowerEndPercent voor ramps). |
HR_ZONE | Hartslag-gedreven intervallen. De trainer past het wattage live aan om de hartslag van de fietser in de doelzone te houden — handig wanneer cardiovasculaire belasting de trainingsmetriek is (herstel, basis, gepolariseerd werk). | Elk interval heeft targetHrZone (1–5). De START-knop toont KOPPEL HRM totdat de HRM is verbonden. |
ROUTE | Helling-gedreven simulatieritten. De trainer volgt het hoogteprofiel uit routeProfile; de fietser kiest cadans en versnelling. | Intervallen zijn meestal leeg of bevatten één placeholder op volledige lengte. De daadwerkelijke ritinhoud staat in routeProfile. |
Het variant-veld selecteert een subvorm:
STANDARD(standaard) — gewone gestructureerde workout.RAMP_TEST— gemarkeerd als FTP-test. Vraagt de fietser om FTP bij te werken aan het einde van de rit en wordt uitgesloten van trainingsbelasting-samenvattingen (zodat een maximale inspanning de wekelijkse belastingsgrafiek niet scheeftrekt).
Packs (.ytwpack)
Wanneer je een samengestelde batch workouts in één keer wilt, bundelt het .ytwpack-formaat veel .ytw-bestanden samen met een per-pack manifest. Een .ytwpack is onder de motorkap een ZIP-archief — hernoem naar .zip en pak uit om de inhoud te zien, of importeer via de pack-installer van Your Trainer om elke workout in één tik geïnstalleerd te krijgen.
Een .ytwpack-archief bevat:
manifest.jsonin de archief-root — de pack-niveau metadata die hieronder wordt beschreven.- Eén
<slug>.ytwper workout in de pack, waarbij elk bestand het hierboven gedocumenteerde schema volgt.
Het pack-manifest bevat genoeg info voor een installatie-beslisblad zonder elke workout te hoeven openen. Velden op hoofdniveau:
| Veld | Type | Beschrijving |
|---|---|---|
schema_version vereist | integer | Momenteel altijd 1. |
pack_id vereist | string | Stabiele kebab-case identifier (bijv. sweet-spot). |
name vereist | string | Weergavenaam getoond in de pack-catalogus. |
description vereist | string | Samenvatting van één of twee zinnen die zichtbaar is voordat de fietser tikt om te installeren. |
version vereist | string (SemVer) | MAJOR.MINOR.PATCH, optioneel -prerelease. Patch voor contentfixes; minor voor toegevoegde workouts; major voor breaking schemawijzigingen. Ingebed in de gepubliceerde bestandsnaam (v1.0.2.ytwpack). |
content_hash vereist | string | sha256: over de op slug gesorteerde concatenatie van de bytes van elk .ytw-bestand. Stabiel over regeneraties van ongewijzigde content; verandert telkens wanneer de workouts erin veranderen. |
generated_at vereist | string (ISO 8601) | UTC-tijdstempel. |
set vereist | string | power of hr-zone — de workoutfamilie waartoe deze pack behoort. |
category vereist | string | Sub-taxonomie binnen de set (bijv. sweet-spot). |
workout_count vereist | integer | Aantal .ytw-entries in de pack. |
total_ride_time_seconds vereist | integer | Som van de duur van elke workout in de pack. |
experience_level vereist | string | Berekend uit het moeilijkheidsbereik van de inhoud — één van beginner / intermediate / advanced / mixed. Kleine letters in het wire-formaat; de app maakt er hoofdletters van voor weergave. |
hrm_required vereist | boolean | True als een workout in de pack HR_ZONE gebruikt. |
type_mix vereist | object | Percentage per categorie van de totale rittijd (telt op tot 100). Bepaalt de in-app type-mix donut op het installatieblad. |
duration_histogram vereist | object | Workoutaantal per duurbak: 0-30, 30-60, 60-90, 90+ (minuten). Bepaalt de duurgrafiek op het installatieblad. |
contents vereist | array | Volledige entries per workout — superset van de entryvorm uit het library-manifest; elke entry bevat slug, name, duration_seconds, samenvattingsmetrieken, plus een sparkline-array voor thumbnail-rendering. |
Twee installatiepaden voor een gedownloade .ytwpack:
- Installatie met één tik in Your Trainer — open de
.ytwpackin het deelmenu, Your Trainer leest het pack-manifest, toont wat erin zit (type-mix, duurhistogram, totale rittijd) en installeert elke workout tegelijk. Beschikbaar zodra de in-app pack-installer wordt uitgebracht. - Handmatig uitpakken — hernoem naar
.zip(of pak direct uit met een willekeurige archieftool) en deel daarna elke.ytwéén voor één naar Your Trainer via het deelmenu.
Voorbeeld pack-manifest (ingekorte contents voor leesbaarheid):
{
"schema_version": 1,
"pack_id": "sweet-spot",
"name": "Sweet Spot",
"description": "26 sweet-spot sessions across classic intervals, sustained stacks, and over-unders.",
"version": "1.0.2",
"content_hash": "sha256:9432a3a76015158dc71ec63…",
"generated_at": "2025-05-28T00:00:00Z",
"set": "power",
"category": "sweet-spot",
"workout_count": 26,
"total_ride_time_seconds": 119340,
"experience_level": "intermediate",
"hrm_required": false,
"type_mix": { "sweet-spot": 100 },
"duration_histogram": { "0-30": 0, "30-60": 5, "60-90": 12, "90+": 9 },
"contents": [
{ "slug": "sweet-spot-3x10min-at-88pct-ftp", "name": "Sweet Spot 3×10min @ 88% FTP", "duration_seconds": 3300, "tss": 55.8, "intensity_factor": 0.764, "sparkline": […] },
{ "slug": "sweet-spot-3x15min-at-90pct-ftp", "name": "Sweet Spot 3×15min @ 90% FTP", "duration_seconds": 4500, "tss": 81.6, "intensity_factor": 0.808, "sparkline": […] }
/* … 24 more workouts … */
]
}
Library- & pack-manifests
Twee manifests worden gepubliceerd naast de downloadbare artefacten. Beide zijn platte JSON; beide worden beschreven door JSON Schema-documenten die je direct kunt ophalen.
| URL | Wat het opsomt | JSON Schema |
|---|---|---|
library/manifest.json |
Elke samengestelde .ytw in de library — metadata per workout voor browse- / search- / filterclients. Somt ook de beschikbare .ytwpack-downloads op (bestandspad, versie, content-hash, type-mix samenvatting, icoon-URL). |
/schemas/workout-manifest.json |
packs/manifest.json |
Pack-catalogus endpoint: elke gepubliceerde .ytwpack met samenvattingsmetadata. Dezelfde entryvorm per pack als de packs-array van het library-manifest; de in-app Pack Library haalt dit op bij een door de fietser gestarte refresh. |
/schemas/workout-manifest.json |
(binnen elke .ytwpack) |
Per-pack manifest meegeleverd als manifest.json in de archief-root — de bovenstaande tabel documenteert de vorm ervan. |
/schemas/workout-pack-manifest.json |
Als je tooling bouwt die de library consumeert — een aangepaste workout-browser, een workout-converter die richt op .zwo, een coach-dashboard dat packs ontsluit — dan zijn dit de contracten om tegen te valideren. Dezelfde entryvorm per workout verschijnt in de contents- / workouts-arrays van beide manifests, dus een client die het ene aankan, kan ook het andere aan.
Uitgewerkte voorbeelden
Ramp-interval
Een warming-up van 5 minuten die via lineaire interpolatie oploopt van 40 % FTP naar 75 % FTP:
{
"id": "rampup",
"duration": 300,
"targetPowerPercent": 40,
"targetPowerEndPercent": 75,
"intensityZone": "Z1",
"label": "Ramp up",
"intervalType": "WARMUP"
}
Over-under
Drie sets van 2 minuten op 95 % FTP / 1 minuut op 105 % FTP, uitgedrukt als een herhalingsgroep:
{
"repeat": 3,
"intervals": [
{ "id": "under", "duration": 120, "targetPowerPercent": 95, "intensityZone": "Z4", "label": "Under" },
{ "id": "over", "duration": 60, "targetPowerPercent": 105, "intensityZone": "Z5", "label": "Over" }
]
}
HF-Zone workout
Uithoudingsvermogen-rit van 30 minuten in Z2 met een Z4-surge van 3 minuten in het midden:
{
"programId": "hr-z2-with-surge",
"programName": "Z2 with a Z4 surge",
"description": "Steady Zone 2 with a single 3-minute Zone 4 surge.",
"totalDuration": 1800,
"workoutType": "HR_ZONE",
"primaryLocale": "en",
"intervals": [
{ "id": "warmup", "duration": 300, "targetHrZone": 1, "intensityZone": "Z1", "label": "Warmup", "intervalType": "WARMUP" },
{ "id": "endure1", "duration": 600, "targetHrZone": 2, "intensityZone": "Z2", "label": "Endurance" },
{ "id": "surge", "duration": 180, "targetHrZone": 4, "intensityZone": "Z4", "label": "Surge" },
{ "id": "endure2", "duration": 420, "targetHrZone": 2, "intensityZone": "Z2", "label": "Endurance" },
{ "id": "cooldown","duration": 300, "targetHrZone": 1, "intensityZone": "Z1", "label": "Cooldown", "intervalType": "COOLDOWN" }
]
}
Meertalig
Het minimale Sweet Spot voorbeeld met EN + DE + NL strings-blokken. Dezelfde workout, drie native ervaringen:
{
"programId": "my-sweet-spot",
"programName": "Sweet Spot 30",
"description": "A short sweet-spot workout.",
"totalDuration": 1800,
"workoutType": "POWER",
"primaryLocale": "en",
"intervals": [
{ "id": "warmup", "duration": 300, "targetPowerPercent": 50, "intensityZone": "Z1", "label": "Warmup", "intervalType": "WARMUP" },
{ "id": "work", "duration": 1200, "targetPowerPercent": 88, "intensityZone": "Z3", "label": "Sweet Spot", "intervalType": "INTERVAL",
"cues": [
{ "offsetSec": 0, "text": "Settle in" },
{ "offsetSec": 600, "text": "Halfway" }
]
},
{ "id": "cooldown", "duration": 300, "targetPowerPercent": 50, "intensityZone": "Z1", "label": "Cooldown", "intervalType": "COOLDOWN" }
],
"strings": {
"en": {
"name": "Sweet Spot 30",
"description": "A short sweet-spot workout.",
"labels": { "warmup": "Warmup", "work": "Sweet Spot", "cooldown": "Cooldown" },
"cues": { "work:0": "Settle in", "work:1": "Halfway" }
},
"de": {
"name": "Sweet Spot 30",
"description": "Ein kurzes Sweet-Spot-Training.",
"labels": { "warmup": "Aufwärmen", "work": "Sweet Spot", "cooldown": "Ausrollen" },
"cues": { "work:0": "Locker einrollen", "work:1": "Halbzeit" }
},
"nl": {
"name": "Sweet Spot 30",
"description": "Een korte sweet-spot-training.",
"labels": { "warmup": "Inrijden", "work": "Sweet Spot", "cooldown": "Uitrijden" },
"cues": { "work:0": "Rustig inrijden", "work:1": "Halverwege" }
}
}
}
Veelvoorkomende valkuilen
- Ontbrekende
idop intervallen. De runtime accepteert intervallen zonderid, maar dan verlies je je vertaling-ingangen —strings.<locale>.labelsen de cue-key map vertrouwen er beide op. Als je van plan bent een workout te verzenden metstrings, geef dan elk interval eenid. - Niet-overeenkomende cue-sleutels. Het cue-key patroon is
<intervalId>:<cueIndex>— nul-geïndexeerd. De derde cue op hetwork-interval iswork:2, nietwork:3. - Zowel
targetPowerPercentalstargetHrZoneop hetzelfde interval. Ze zijn wederzijds uitsluitend — houd één van beide aan op basis van deworkoutTypevan de ouder. - Vergeten
intervalTypeop warming-ups en cooling-downs. De standaard isINTERVAL— stel explicietWARMUP/COOLDOWNin zodat de analyticslaag die blokken niet meerekent in het werktotaal. - Opgeblazen
totalDuration. Onschadelijk tijdens het schrijven — Your Trainer berekent het opnieuw uitintervalsbij het opslaan. Maar het is de moeite waard om het kloppend te krijgen voordat je deelt, omdat sommige externe tools het veld letterlijk weergeven. - Verkeerde
intensityZone-token. Moet één vanZ1–Z5als string zijn."Z6"of"3"zal niet de juiste kleur renderen op de terreinvisualisatie.
Referentie
- De AI Workout Coach genereert geldige
.ytwJSON uit een beschrijving in platte tekst — handig als startpunt dat je daarna handmatig bewerkt. - Voor deterministische programmatische authoring (geen LLM): Your Trainer MCP — Integratordocumentatie behandelt
build_workout_from_intentendecompose_workout, die naar / vanuit dit schema componeren. - Voor workouts die niet importeren: Probleemoplossing → Workout- & route-imports.
- Voor zonedefinities: Trainingszones.
- Voor onbekende termen: Woordenlijst.
- De workouts die met Your Trainer worden meegeleverd benutten elke vorm die het schema ondersteunt — er één bekijken in een teksteditor (exporteer vanuit de visuele editor) is een snelle manier om een echt voorbeeld van elk veld te zien.
Voor programmatische format-spec lookups (veldtabellen, voorbeelden, constraints, glossarium) is de canonieke machine-leesbare bron de Your Trainer MCP — roep get_format_spec, get_canonical_examples, get_format_constraints of get_format_glossary aan vanuit elke MCP-client. Deze tools serveren uit dezelfde kennisbasis die deze pagina voedt; wijkt een tool-antwoord ooit af van deze pagina, dan is de MCP canoniek en is deze pagina verouderd.
JSON Schemas (machineleesbare contracten voor clients die op deze formaten bouwen):
/schemas/workout-manifest.json— library-manifest + entryvorm per pack (dekt zowellibrary/manifest.jsonalspacks/manifest.json)./schemas/workout-pack-manifest.json— demanifest.jsondie binnen elke.ytwpackwordt meegeleverd./schemas/workout-intent.json— gestructureerde-intent vorm gebruikt door workout-authoring tools die naar.ytwcomponeren.