The science behind how we calculate training load for hybrid athletes.
Getting up and running with LOAD takes just a few minutes:
1. Connect your apps (Settings page)
- Strava — for cardio sessions (running, cycling, swimming). Connect via OAuth, and LOAD syncs your last 12 weeks automatically.
- Hevy — for gym/strength sessions. Paste your API key from hevy.com/settings (Developer tab).
- Polar — for cardio + sport sessions. Connect via OAuth.
- You can also log sessions manually from the Log page.
2. Build your first plan (Mixer page)
- Pick which activities you do and set your weekly volumes using the sliders.
- The Mixer generates a balanced plan across the week, respecting rest days and your target hours.
- Save the plan — your Dashboard will now show planned vs actual progress.
3. Train and track
- As you train, sessions sync automatically (or log manually).
- Your Dashboard shows real-time progress: hours, load, and pace projections.
4. Close the week
- At the end of the week (or when you're done), close the week from the Dashboard.
- Your bias level adjusts based on consistency, and you can build next week's plan.
LOAD follows a simple weekly loop:
Monday → Saturday: Train against your plan
- Your Dashboard shows the plan for each day — what activity, how long, at what intensity.
- Sessions sync from Strava/Hevy/Polar automatically, or you log them manually.
- The "On pace" projections tell you if you're tracking toward your weekly targets.
End of week: Close and plan ahead
- When you're done training for the week, close it from the Dashboard banner.
- LOAD reviews your adherence (did you hit your target hours?) and adjusts your bias level.
- If you hit the advance threshold, your bias goes up — next week's plan will push a bit harder.
- If you fell short, bias holds or retreats — next week eases off so you stay consistent.
- Then build next week's plan in the Mixer. The new plan automatically reflects your updated bias.
The key insight: You don't need to manually increase difficulty. Just train consistently, close your weeks, and LOAD progressively increases your plan volume over time. Miss a week? It backs off. Hit a streak? It pushes harder.
Bias is your training intensity level — a number from 0% to 100% that scales how hard your weekly plan is.
How it works:
- At 0% bias, your plan uses base session durations (e.g., a 60-minute ride stays 60 minutes).
- At 50% bias, sessions are about 25% longer than base.
- At 100% bias, sessions are 50% longer — the maximum progression.
How it changes:
Each week when you close, LOAD checks: did you hit your target hours?
- Yes (above advance threshold): Bias goes up (typically +3 to +5 points). Your next plan gets slightly harder.
- Close but not quite (between thresholds): Bias holds. Same difficulty next week.
- Missed significantly (below hold threshold): Bias retreats (typically -2 to -3 points). Next week eases off.
Why it matters:
Bias is what makes LOAD adaptive. Instead of following a static plan that doesn't know if you're struggling or thriving, your plan automatically adjusts each week. Train consistently for 4 weeks and you'll see your bias climb — meaning you're genuinely getting fitter and your plan reflects it.
You can see your bias history and adjust the intensity slider (how strict the thresholds are) on the Progress page.
LOAD supports three integrations for automatic session import:
Strava (cardio — running, cycling, swimming, etc.)
- Go to Settings → Connections → click "Connect Strava"
- Authorize LOAD in Strava's OAuth screen
- Your last 12 weeks of activities sync immediately
- New activities sync automatically going forward
- Use the "Re-sync" button if you need to pull recent changes
Hevy (gym/strength training)
- Go to hevy.com/settings → Developer tab → copy your API key
- Go to Settings → Connections → paste the key and click "Connect"
- Your gym sessions sync with full exercise details and tonnage
- Re-sync anytime to pull new sessions
Polar (cardio + sport)
- Go to Settings → Connections → click "Connect Polar"
- Authorize LOAD in Polar's OAuth screen
- Supports running, cycling, swimming, and strength training
Priority rules: If you log sessions from multiple sources, PUSH-imported gym sessions always take priority. Cardio sessions from integrations are never overwritten by manual imports. Deduplication is handled by external activity ID.
LOAD tracks two types of training stress that reflect different physiological systems:
Cardio Load measures cardiovascular and metabolic strain — how hard your heart and lungs worked. It uses the TRIMP (Training Impulse) method based on your heart rate during the session.
Muscle Load measures neuromuscular stress — the structural and metabolic fatigue imposed on your muscles, tendons, and nervous system. It's calculated from RPE and duration for gym sessions, or from tonnage for PUSH imports.
Both matter for hybrid athletes. A triathlete who also lifts needs to track both systems to avoid overtraining either one.
Cardio Load uses the TRIMP (Training Impulse) formula developed by Banister (1975):
Cardio Load = duration (min) × HR_intensity² × 1.92Where HR intensity = (avg HR - resting HR) / (max HR - resting HR).
The squared term means higher heart rates produce exponentially higher load — a session at 85% max HR generates significantly more load than one at 65%. This reflects the reality that high-intensity work creates disproportionately more fatigue.
Your resting HR and max HR come from your profile (Settings → Heart rate zones).
When HR data is missing or incomplete: If a session has no avg HR (some imports or manual logs skip it), OR your profile is missing calibrated HR zones, LOAD falls back to a simple duration-only estimate (duration × 0.5). This is deliberately conservative — it avoids silently computing TRIMP against population-average HR defaults, which would give physiologically wrong numbers for anyone outside that range (e.g. older athletes with lower true max HR). Once you set your HR zones, all new sessions will use the full TRIMP formula.
There are two methods depending on data source:
RPE-based (manual logging and Strava imports):
Muscle Load = duration (min) × RPE × 0.18RPE (Rate of Perceived Exertion) on a 1-10 scale captures overall session difficulty. The 0.18 coefficient calibrates the output to be comparable in magnitude to TRIMP values. For Strava gym imports where RPE isn't available, a default RPE of 7 is used.
Tonnage-based (PUSH imports):
Muscle Load = tonnage (kg) / 400 + bodyweight_reps × 0.1 + PB_count × 0.5This method uses actual weight moved, giving credit for personal bests and bodyweight exercises. A typical 5,000-8,000 kg session produces 12-20 load points, equivalent to a moderate cardio session.
Note: For bilateral dumbbell exercises (e.g. Dumbbell Shoulder Press, DB Incline Fly), LOAD automatically doubles the weight in tonnage calculations — see the section below.
When you log a bilateral dumbbell exercise in PUSH (e.g. "Dumbbell Shoulder Press"), you enter the weight of one dumbbell — but you're lifting two simultaneously. LOAD detects this and automatically doubles the weight for tonnage calculations.
Detection rules:
- Exercise name contains "Dumbbell" or "DB " (with trailing space to avoid false positives)
- Exercise is not flagged as unilateral by PUSH (i.e. no left/right side tracking)
Example:
Dumbbell Shoulder Press: 20kg × 10 reps × 3 setsTonnage = 20kg × 2 (bilateral) × 10 reps × 3 sets = 1,200 kgWithout the correction, this would incorrectly show as 600 kg — underestimating muscle load by 50%.
Unilateral exercises are excluded. Exercises like "Single Arm DB Row" or "Dumbbell Walking Lunge" are tracked per side in PUSH (with left/right flags). LOAD deduplicates these by keeping only one side per set and does not double the weight, since each side is already recorded individually.
Barbell, cable, and machine exercises are unaffected — their logged weight already represents the full load being moved.
Endurance activities impose muscular stress beyond what heart rate-based TRIMP captures. LOAD calculates a hybrid muscle load for cardio sessions based on the activity's muscular demands:
| Activity | Muscle Factor | Rationale |
|---|---|---|
| Swimming | 25% of cardio load | Upper body + core recruitment, high metabolic fatigue |
| Running | 20% of cardio load | Eccentric damage from ground impact, legs + core |
| Tennis | 25% of cardio load | Explosive deceleration, shoulder load, full-body |
| Cycling | 10% of cardio load | Concentric-dominant, localized quads/glutes |
| Zwift | 10% of cardio load | Same as cycling |
| Walking | 5% of cardio load | Low intensity, mild eccentric loading |
These factors are based on sports science research on eccentric muscle damage profiles, metabolic fatigue patterns, and muscle recruitment across endurance sports. Swimming and tennis rank highest because they involve the most muscle groups and eccentric loading. Cycling ranks lowest because it's primarily concentric (pushing, not braking).
The Fatigue Load shown on your dashboard is not a simple sum of cardio + muscle load. Muscle load is multiplied by 1.5× before being added:
Fatigue Load = Cardio Load + (Muscle Load × 1.5)This weighting corrects two things:
1. Scale normalization (~1.3×): TRIMP and muscle load formulas produce numbers on slightly different scales. A 1-hour threshold cardio session generates ~89 TRIMP points, while a 1-hour gym session at RPE 7 generates ~76 muscle points. The formulas need normalizing to represent equivalent efforts equally.
2. Recovery premium (~0.2×): Muscular fatigue from strength training takes 48-72 hours to recover from (due to muscle fiber damage and CNS fatigue), compared to 24-48 hours for cardiovascular fatigue. A gym session's "cost" to your weekly recovery budget is higher per unit of output.
This 1.5× factor is supported by converging evidence from multiple sources:
- Friel's TrainingPeaks TSS assigns strength sessions 80-90 TSS/hour vs ~60 TSS/hour for moderate endurance (ratio ~1.3-1.5×)
- Uphill Athlete coaching framework calibrates strength TSS based on observed recovery time
- Foster's session-RPE research shows TRIMP underestimates perceived load at high intensities (including resistance training)
- Neuromuscular recovery research confirms peripheral fatigue from resistance training persists 2-3× longer than from moderate endurance work
Your weekly capacity benchmark is personalized based on your profile:
Base capacity by fitness level:
- Beginner: 4-7 hours/week, 2 gym + 3 cardio sessions
- Intermediate: 6-11 hours/week, 3 gym + 4 cardio sessions
- Advanced: 9-16 hours/week, 4 gym + 6 cardio sessions
Modifiers applied:
- Age: Recovery capacity drops ~1% per year after age 35 (capped at 85% for athletes over 50)
- Training experience: Each year of training adds 5% adaptation capacity (up to 30% bonus)
- Body weight: Athletes over 85kg get a 8% reduction for impact activities; under 65kg get a 5% bonus
Sources: Friel's Triathlete's Training Bible, ACSM exercise prescription guidelines, published Half Ironman training plans, and strength standards by bodyweight.
LOAD assesses your weekly training against a personalized ceiling (blended from your recent history and athlete profile):
| Zone | % of Ceiling | Meaning |
|---|---|---|
| Recovery | < 50% | Deload week. Good for recovery blocks. Consider adding a session if feeling fresh. |
| Optimal | 50-85% | Ideal training zone. Well within your capacity for consistent, sustainable progress. |
| Hard | 85-110% | Pushing your limits. Prioritize sleep, nutrition, and recovery. Limit to 1-2 weeks before backing off. |
| Overload | > 110% | Above your estimated ceiling. Risk of overtraining, injury, or illness. Consider cutting 1-2 sessions. |
The ACWR (Acute-to-Chronic Workload Ratio) also applies regardless of zone: a ratio above 1.3 suggests you're ramping up faster than your body has adapted to. Sudden jumps in training load are the strongest predictor of injury in endurance athletes. LOAD shows your projected ACWR in the Mixer so you can adjust before committing.
The Mixer plans your week in hours (which is intuitive — athletes think "I have 90 minutes for a ride"), but LOAD also estimates the fatigue load each planned session will produce, based on activity type, duration, and planned intensity.
The bridge formula (based on TrainingPeaks TSS methodology):
For cardio sessions, LOAD estimates TRIMP using typical heart rate intensity by level:
| Intensity | HR Reserve % | Factor | Example (60min ride) |
|---|---|---|---|
| Easy | ~60% HRR | 0.45 | ~22 cardio load |
| Moderate | ~72% HRR | 0.65 | ~49 cardio load |
| Hard | ~85% HRR | 0.82 | ~77 cardio load |
The hybrid muscle component is added automatically (same factors as actual sessions).
For gym sessions, LOAD uses the sRPE method (Foster et al., 2001) — duration × estimated RPE × 0.18 — with RPE estimates of 5 (easy), 7 (moderate), or 9 (hard).
Calibrated load estimation: When you have recent training history, the Mixer uses your actual load-per-minute rate from recorded sessions instead of theoretical formulas. An intensity multiplier adjusts for planned effort: hard sessions are estimated at 1.2x and easy sessions at 0.85x of the calibrated rate. This means sliding an activity from moderate to hard visibly increases the Est. Load even at the same duration.
All estimates use the same 1.5x muscle weighting as actual sessions, so planned loads are directly comparable to recorded loads.
ACWR (Acute-to-Chronic Workload Ratio) is a sports science metric developed by Tim Gabbett that predicts injury risk by comparing your current week's training load to your recent average.
ACWR = This week's load ÷ Average load over the past 4 weeks| ACWR Range | Meaning |
|---|---|
| < 0.8 | Undertraining — you may be losing fitness. Good if intentional deload. |
| 0.8 – 1.3 | "Sweet spot" — lowest injury risk. Your body is adapted to this workload. |
| 1.3 – 1.5 | Caution zone — elevated injury risk. Consider reducing intensity. |
| > 1.5 | Danger zone — high injury risk from load spike. Strongly recommend reducing volume. |
The key insight from Gabbett's research is the Training-Injury Prevention Paradox: high chronic load is actually protective. Athletes who consistently train at high loads are more resilient to load spikes. The danger comes from sudden jumps relative to what your body is adapted to.
LOAD computes your projected ACWR in the Mixer so you can see the injury risk of your planned week before committing to it.
Sources: Gabbett (2016), Hulin et al. (2014), Blanch & Gabbett (2016).
Your training baseline is calculated from completed weeks only (Monday to Sunday). The current, in-progress week is excluded.
By default, LOAD looks at the last 3 completed weeks to compute your baseline averages (sessions per week, duration, load per week). This means:
- On a Wednesday, your Mon/Tue/Wed sessions from this week are not included in the baseline — only the 3 full weeks before this Monday count.
- On Sunday evening, that week is still treated as the current week until Monday arrives.
Why? Including a partial week would dilute the averages. If it's Tuesday and you've logged 1 session, a naive average would suggest you only train 0.3x/week, when in reality you train 4x/week. By using only completed weeks, the baseline stays accurate regardless of which day you check it.
The same logic applies to the ACWR chronic load — it averages your fatigue load over the 4 completed weeks prior to the current one, so a mid-week check won't distort the ratio.
The "On pace for" projection on the dashboard estimates your end-of-week total based on what you've logged so far.
Projected hours = (hours trained so far ÷ days elapsed) × 7For example, if you've trained 3 hours by Thursday (day 4 of the week), the projection would be:
3h ÷ 4 days × 7 = ~5.3hWhen does it show?
- Only on the current week (not past weeks)
- Only Monday through Saturday — on Sunday the week is effectively complete, so the actual total is your final number
- Only when you've logged at least one session — showing "On pace for 0h" on Monday morning isn't useful
Is it accurate? It's a simple linear projection that assumes training is evenly distributed across the week. If you front-load or back-load your training, the mid-week projection will over- or under-estimate. It's meant as a rough sanity check, not a precise forecast.
The same pace logic is applied to the Fatigue Load and Calories cards.
LOAD uses a bias level (0–100%) to scale your planned session durations. Each week, a review determines whether your bias advances, holds, or retreats.
How bias affects your plan:
The bias scaling formula is: session duration = base duration x (1 + bias/100 x 0.5)
- At 0% bias, sessions use your base typical durations (no scaling).
- At 50% bias, sessions are 25% longer than base.
- At 100% bias, sessions are 50% longer than base.
For example, at 27% bias, a 60-minute base ride becomes ~68 minutes. This means every good week (bias +4) adds roughly 2% more volume to your next plan.
Weekly review logic:
| Outcome | When | Effect |
|---|---|---|
| Advance | You hit the advance threshold (e.g. 81% of target hours at intensity 5) | Bias increases by the advance rate (e.g. +4% at intensity 5) |
| Hold | Between hold and advance thresholds | Bias stays the same |
| Retreat | Below the hold threshold (e.g. < 67% at intensity 5) | Bias decreases by the retreat rate (e.g. −2% at intensity 5) |
Streaks matter. Three or more consecutive good weeks trigger a streak bonus — a larger bias advance on top of the base rate. Consecutive bad weeks increase the retreat penalty.
The intensity slider (1–10) on the Progress page controls how strict the thresholds are and how fast bias moves. See the "Training Intensity slider" section below for details.
Every Monday, LOAD automatically reviews the previous week when you visit the Dashboard. Here’s the full flow:
1. Auto-review on Monday
When you open the Dashboard after a new week starts, LOAD detects that last week hasn’t been reviewed yet. It triggers a review automatically — you’ll see a brief "Reviewing last week…" spinner.
2. Review banner appears
After the review completes, a banner shows your results:
- Outcome (advanced / held steady / retreated) with the bias change
- Stats: actual hours vs target, adherence %, and your current streak
- Bias change: your bias before and after the review
- CTA: if you don’t have a plan for the current week yet, a button links you to the Mixer to build one
3. After building a plan
Once you save a plan in the Mixer, the banner collapses to a compact pill showing the outcome and "Plan active" — staying out of your way for the rest of the week.
The review is idempotent. If it runs twice (e.g. from both the Dashboard and the Progress page), it won’t double-count. The API checks last_reviewed_week and skips if already reviewed.
First-time users see a welcome card instead, with a link to set up activities and build their first plan.
Yes. If it’s late in the week (e.g. Sunday) and you want to see next week’s plan before Monday arrives, you can close the current week early.
How it works:
1. On the Dashboard, when you have an active plan, a "Close this week & plan ahead" button appears below the review pill.
2. Clicking it runs the weekly review on your current week’s data (everything logged so far).
3. The review banner updates to show your results for this week — with a CTA to "Build next week’s plan".
4. That link opens the Mixer in next-week mode, where the generated plan is saved to next Monday instead of the current week.
Important notes:
- The review uses your actual training through today. If you close on Saturday, Sunday’s sessions won’t be counted in the review (they still count for load calculations though).
- The bias adjustment happens immediately, so next week’s plan will use the updated bias level.
- On Monday, the auto-review won’t fire again — the system knows the week was already reviewed.
- You can close any day, but it’s most useful from Thursday onward when most of the week’s training is done.
The Mixer generates a 7-day training plan based on your activity preferences, profile, and recent training data.
Inputs:
- Activity sliders (0–10): control how many sessions of each activity type per week. 0 = off, 5 = typical, 10 = maximum frequency. Higher levels (6+) also make sessions progressively longer (level 6: +5%, level 7: +10%, up to level 10: +25%).
- Activity settings: typical duration per activity, injury flags, and enabled/disabled status.
- Profile: target hours range, fitness level, bias level, and athlete benchmarks.
- Recent sessions: used to calibrate estimated loads and compute ACWR.
Plan generation steps:
1. Session count: sliders are converted to a target number of sessions per activity type.
2. Duration scaling: each session starts with your typical duration, then two scaling layers apply:
- Level scaling: slider levels above 5 add 5% per level (level 7 = +10%).
- Bias scaling: your progression bias stretches all durations (at 27% bias, sessions are ~14% longer than base). These stack multiplicatively.
3. Session redistribution: each activity card shows +/− buttons. You can redistribute the total volume across more or fewer sessions (e.g. 4x45min → 5x36min) while keeping hours fixed.
4. Day spreading: sessions are distributed across the week to avoid stacking too many on one day. Rest days are preserved. The algorithm penalizes putting the same activity on consecutive days and heavily penalizes duplicates on the same day.
5. Load calibration: estimated load for each session uses your actual load-per-minute from recent training, not generic formulas. An intensity multiplier is applied (hard sessions: 1.2x, easy: 0.85x) so higher intensity plans show a higher estimated load.
6. ACWR check: the plan’s total projected load is compared against your 4-week chronic load to compute ACWR. The ACWR uses raw calibrated minutes (without intensity multiplier) so it’s consistent with your historical recorded loads.
7. Active recovery: on rest days, a short recovery walk may be added (customizable).
Drag-and-drop: after generation, you can drag sessions between days in the week preview to fine-tune the schedule before saving.
The 7-day grid on the Dashboard shows both your planned sessions (from the Mixer) and your actual completed sessions side by side.
Visual cues:
- PLAN (dashed border): sessions from your saved weekly plan that haven’t happened yet. Past planned sessions are dimmed to 50% opacity.
- DONE (solid border + ✓): sessions you’ve actually logged (via Strava, PUSH, or manual entry). These show duration, distance, and load.
- Active recovery walks from the plan are hidden to reduce clutter.
This lets you see at a glance: which planned sessions you’ve completed, which are still coming up, and any unplanned sessions you added.
If you don’t have a plan saved for the current week, a "Build a plan" link appears in the header.
Recovery weeks are periodic deload weeks where your training volume is intentionally reduced by 30–50%. They’re a standard practice in endurance training (Friel, Daniels) to allow physiological adaptation, prevent overtraining, and maintain psychological freshness.
How they work in LOAD:
- In Settings → Recovery Weeks, you choose how often (every 3–6 weeks) and how much volume to reduce (50–70%).
- When you activate a recovery week, the Mixer generates a plan with reduced session durations.
- Your bias holds steady during recovery — no advance, no retreat, streak resets to 0. You’re not penalized for taking a planned deload.
- After you close the recovery week, it automatically deactivates and your next week returns to normal volume.
When to use one:
- Every 3–4 weeks as a standard periodization cycle
- When you feel fatigued, flat, or unmotivated
- After a streak of high-volume weeks
- LOAD will suggest a recovery week when enough time has passed since your last one
Recovery vs Taper: Taper is race-specific (1–3 weeks before a goal event). Recovery weeks are scheduled throughout your training regardless of race dates. If both are active, taper takes priority.
The intensity slider (1–10) on the Progress page controls how aggressively LOAD moves your bias level each week. It replaces the old separate "Training Phase" and "Fine-Tune" controls with a single, unified slider.
Phase mapping:
| Slider | Phase | Description |
|---|---|---|
| 1–2 | Recover | Very forgiving. Almost impossible to lose progress. For injury recovery or off-season. |
| 3–5 | Base | Steady, sustainable progress. The default. Forgives the occasional bad week. |
| 6–7 | Build | Faster gains but punishes inconsistency. Good 3–5 months before a race. |
| 8–10 | Peak | Aggressive. Demands consistency. For the final 6–10 weeks before race day. |
What changes with higher intensity:
- Advance threshold increases — you need a higher % of your target hours to count as a good week (68% at intensity 1 vs 93% at intensity 10)
- Advance rate increases — good weeks move your bias further (1%/wk at intensity 1 vs 11%/wk at intensity 10)
- Retreat penalty increases — bad weeks cost more (1%/wk at intensity 1 vs 5%/wk at intensity 10)
- Streak bonus increases — 3+ consecutive good weeks earn a bigger bonus
When does it take effect? Immediately after saving. The new thresholds and rates apply to the next Monday review.
What does it NOT affect? The intensity slider does not directly change your planned session durations or load calculations. However, because it controls how fast your bias advances, it indirectly affects volume over time. A higher intensity setting means faster bias growth, which means sessions get longer sooner through the bias scaling system.
LOAD supports 12 goal profiles grouped by sport:
Triathlon: Full Ironman (12–20h/wk), Half Ironman 70.3 (8–14h/wk), Olympic Triathlon (6–10h/wk), Sprint Triathlon (5–8h/wk)
Running: Ultra Marathon (10–18h/wk), Marathon (8–14h/wk), Half Marathon (5–10h/wk), 10K (4–7h/wk), 5K (3–5h/wk)
Cycling: Century Ride (8–14h/wk), Gran Fondo (6–12h/wk)
General: General Fitness (4–10h/wk)
When you select a profile, LOAD auto-fills your weekly hours range based on research-backed training volumes. You can always customize the range after selecting.
Profile selection happens during onboarding and can be changed anytime in Settings.
When gym/strength is enabled in the Mixer, you can optionally set a focus area: Full Body, Upper Body, or Lower Body.
This is a planning label — it appears on your Plan page to help you remember what type of gym session you planned for each day. It does not change the load calculation (gym load uses the sRPE method regardless of focus area).
The focus is saved with your plan and visible on the Plan page as a badge next to the gym session.
LOAD has two notification channels:
In-app notifications (bell icon): A bell icon in the navigation bar shows unread notifications with a count badge. Click to see recent notifications. Types include: weekly review reminders, sync confirmations, and system messages. Click a notification to mark it as read.
Sunday email reminders: Every Sunday, LOAD sends a reminder to close your training week and plan ahead — timed to arrive around 9 AM in your local timezone (based on the country in your profile). Reminders only go out if you have not reviewed your week yet. Turn these off in Settings under the Notifications section.
Hevy is a gym workout tracking app. LOAD can import your Hevy workouts automatically.
Setup:
1. Go to hevy.com/settings and find Developer to copy your API key.
2. In LOAD Settings, paste the key in the Hevy (Gym) section and click Connect.
3. LOAD will auto-sync your last 12 weeks of gym workouts.
What gets imported:
- Each Hevy workout becomes a gym session in LOAD.
- Muscle load is calculated from your actual tonnage (sets x reps x weight), not just duration.
- Muscle groups are inferred from exercise names (bench press = chest, squat = legs, etc.).
- Exercise names are stored in the session notes.
Priority rules: If you have gym sessions from multiple sources on the same day, PUSH takes priority over Hevy, and Hevy takes priority over Strava gym activities.
Re-sync: Use the Re-sync recent workouts button in Settings anytime to pull in new workouts. Duplicates are automatically skipped.
Your country is used to determine your timezone, which affects when you receive Sunday email reminders. LOAD sends the weekly review reminder at approximately 9 AM in your local time, not a fixed UTC time.
Your country is auto-detected from your browser settings during onboarding, but you can change it anytime in Settings under the Profile section. The derived timezone is shown below the country selector.
If you see a "[Service] sync has stopped" banner on your Dashboard, it means LOAD can no longer talk to that service and new activities won't appear until you reconnect. The banner is there because silent failure is worse than a noisy error — you'd rather know.
What causes it:
- Strava: you revoked access (Strava settings → My Apps → Revoke), or the refresh token expired / was invalidated server-side. Strava tokens last ~6 hours but auto-refresh indefinitely unless revoked.
- Polar: you revoked access in your Polar Flow account, or the refresh token was invalidated (Polar is stricter about inactivity).
- Hevy: your API key was rotated or deleted at hevy.com/settings → Developer.
What LOAD does automatically:
When a token fails with a 401 (unauthorised), LOAD deletes the stored token row from its database so no further sync attempts hit the external service with a known-bad credential. That's what triggers the banner: you have recent sessions from that source in your last 30 days, but no stored token — so sync would fail if we tried.
How to fix:
Click Reconnect [Service] on the banner (or go to Settings → Connections). You'll run through the same OAuth flow (or API-key paste for Hevy) that you did originally. Your existing sessions are preserved — reconnecting only restores future sync.
Your historical data stays put. The token row being cleared doesn't touch your session history. You won't lose a single logged workout.
When you connect Strava, Polar, or Hevy, LOAD exchanges the OAuth code for an access token (and a refresh token for Strava/Polar). These credentials are encrypted at rest with AES-256-GCM before being written to the database, using a 32-byte key held only in the server environment.
Storage format: Every stored token begins with the prefix enc:v1: followed by a random IV, an authentication tag, and the ciphertext — all base64-encoded. The encryption key is never logged, never sent to the browser, and never included in backups alongside the data.
Rotation: The key can be rotated, but doing so requires a re-encryption migration. Until then, stored tokens are readable only by code running with the correct TOKEN_ENCRYPTION_KEY env var.
What this doesn't protect against: a compromise of the server environment itself (Vercel env vars) would still expose tokens in memory. Encryption-at-rest specifically defends against DB dumps, backup leaks, and read-only service-role compromises.
The OAuth state parameter is HMAC-signed server-side before being sent to the third-party provider. On return, the signature is verified in constant time and any mismatch, tampering, or timestamp older than 10 minutes is rejected.
Before this protection existed, a common web vulnerability would let an attacker who initiated their own Strava OAuth flow substitute your user ID into the callback URL, causing *their* Strava account to be bound to *your* LOAD account (or vice-versa). The HMAC signature makes that impossible: only a server-side callback using our secret can produce a valid state.
This is in addition to defense-in-depth: the /connect routes now require an authenticated session, so the OAuth flow cannot even be initiated by an unauthenticated caller.
Yes. When Strava notifies us that you deauthorized the app or deleted an activity, the webhook POST includes a subscription_id. LOAD verifies this matches the subscription we registered — if it doesn't, the event is rejected.
Without this check, anyone on the internet could POST a forged deauth or activity-delete event and wipe a victim's data. The subscription_id check is fail-closed: if the env var isn't set on the server, every webhook event is rejected.
No. Sentry session replays run with maskAllText: true, which means text content in form fields — including your name and email on Settings and Onboarding — is masked before being uploaded.
Error logs from upstream third-party APIs are also scrubbed: Bearer/Basic auth headers, access tokens, refresh tokens, OAuth codes, and API keys are replaced with [REDACTED] before being written to console or sent to Sentry.
What you can still see in Sentry: error messages, stack traces, your user ID (for correlating issues), and structural details like HTTP status codes and URLs (with query-string secrets stripped).
Every table in the database that holds user data has Row-Level Security (RLS) enabled, with a policy that allows a row to be read or written only if auth.uid() = user_id (or auth.uid() = id for the profiles table).
Policies are evaluated once per query plan (using (select auth.uid())), so RLS overhead is constant regardless of how many rows the query touches.
Some admin operations — like the daily backup job and webhook handlers that lookup users by their Strava athlete_id — need to run with a service-role client that bypasses RLS. Those operations are gated by either a CRON_SECRET header or a webhook signature, and the user they act on is always derived from a verified input (never from a client-supplied parameter).