Files
garmin-coach-to-cal-sync/docs/feasibility.md
2026-06-16 15:14:37 +02:00

15 KiB

Garmin Coach to Edge 1030 Feasibility

Research date: 2026-06-16.

Executive Summary

The most realistic path is an unofficial Garmin Connect API probe:

  1. Read the user's Garmin Connect calendar/training-plan data.
  2. Determine whether Garmin Coach cycling workout steps are exposed in the private training-plan/adaptive-plan JSON.
  3. If step details are present, create a normal Garmin Connect cycling workout and schedule it.
  4. Let the Edge 1030 sync that normal scheduled workout.

What is proven from source documentation/code:

  • The Edge 1030 supports normal Garmin Connect structured workouts and scheduled workouts.
  • python-garminconnect has methods for listing normal workouts, uploading a workout JSON, scheduling/unscheduling workouts, deleting workouts, listing calendar entries, and fetching training/adaptive plan details.
  • The Garmin Connect workout JSON schema for normal workouts is partially represented by python-garminconnect.workout.

What is proven from this user's account probe:

  • Garmin login/token reuse works with python-garminconnect.
  • The account has an active FBT_ADAPTIVE Garmin Cycling Coach plan.
  • The plan/calendar APIs expose dated Coach workout summaries, including name, date, sport type, duration, compact description, training effect label, and workoutUuid.
  • The Garmin Connect browser uses /gc-api/workout-service/fbt-adaptive/{workoutUuid} to fetch the full Coach workout with workoutSegments and workoutSteps.
  • The same detail is available to python-garminconnect through the Connect API path /workout-service/fbt-adaptive/{workoutUuid} without the browser /gc-api prefix.

What is still not proven:

  • Whether the Edge 1030 accepts every cloned target type after sync.
  • Whether the endpoint remains stable as Garmin changes private APIs.
  • Whether a summary-derived approximation is good enough for the user's training use case.

The official Garmin Developer Training API is not a good fit for this use case. It is business/approval-gated, designed to publish third-party workouts/plans to users, and does not provide a documented way to read a personal Garmin Coach/adaptive plan workout from Garmin Connect.

Feasibility Matrix

Feature Supported / Unsupported / Unknown Tested / documented / guessed API/package/source Notes
Edge 1030 runs normal structured workouts from Garmin Connect Supported Officially documented; not tested here Edge 1030 manual: Workouts, Starting a Workout Manual says workouts can be created in Garmin Connect, transferred, scheduled, and run on device.
Edge 1030 shows scheduled workouts in calendar/training plan Supported Officially documented; not tested here Edge 1030 manual: Training Calendar Menu path documented as Training > Training Plan > calendar icon.
Official API can read Garmin Coach/adaptive workouts from personal account Unsupported Documented absence/inferred from API direction Garmin Training API, Health API docs Training API publishes workouts/plans to Garmin Connect; Health API reads health/activity metrics, not Coach workout definitions.
Official API can create/schedule workouts Supported with approval Officially documented Garmin Training API It can publish workouts/training plans to the Garmin Connect calendar.
Official API available for personal app Unsupported for this use case Officially documented Garmin Developer Program FAQ Program is for enterprise/business use and requires approval.
Authenticate unofficially with Garmin Connect Supported/fragile Tested with user's account python-garminconnect login/tokenstore source Login worked and wrote .garmin-tokens. Future Garmin SSO changes remain a risk.
garth as standalone auth library Unsupported/fragile Project README matin/garth README garth says it is deprecated and new logins may not work after Garmin auth changes. Prefer current python-garminconnect probe.
Store/reuse Garmin tokens Supported in source Source-supported; untested here python-garminconnect login and demo Token directory can be loaded/dumped. Probe defaults to .garmin-tokens.
List training plans Supported Tested with user's account Garmin.get_training_plans() Returned one FBT_ADAPTIVE plan: 2026 Fitness Plan test.
Detect active Garmin Coach/adaptive plan Supported/partial Tested with user's account trainingPlanCategory == FBT_ADAPTIVE The active cycling Coach plan is detectable.
Fetch adaptive training-plan detail Supported summary only Tested with user's account get_adaptive_training_plan_by_id() Returns taskList with dated taskWorkout summaries, but no step arrays.
Fetch Coach workout step details for date Supported/fragile private API Tested with user's account /workout-service/fbt-adaptive/{workoutUuid} Returns full workoutSegments/workoutSteps via normal python-garminconnect token auth. Browser shows equivalent /gc-api/workout-service/fbt-adaptive/{workoutUuid}.
Read compact Coach workout summary Supported Tested with user's account Adaptive plan taskList[].taskWorkout Exposes fields such as workoutName, estimatedDurationInSecs, workoutDescription, trainingEffectLabel, workoutPhrase, workoutUuid.
List Garmin calendar entries Supported Tested with user's account get_scheduled_workouts(year, month) Calendar contains fbtAdaptiveWorkout entries with workoutUuid and trainingPlanId.
List normal workouts Supported Tested with user's account get_workouts() Returned normal cycling workouts.
Fetch normal workout by ID Supported in source Source-supported; untested here get_workout_by_id() Useful for schema inspection and clone comparisons.
Create/upload normal cycling workout Supported in source Source-supported; untested here upload_workout(), upload_cycling_workout() Probe includes a minimal dummy cycling workout upload test.
Schedule normal workout on date Supported in source Source-supported; untested here schedule_workout(workout_id, YYYY-MM-DD) This is the preferred path if upload works.
Delete generated workout Supported in source Implemented with guard; not account-tested delete_workout() Probe refuses to delete unless the ID is in the generated-prefix workout list and --confirm is passed.
Unschedule generated workout Supported in source Implemented with guard; not account-tested unschedule_workout() Probe refuses to unschedule unless the scheduled ID is in the generated-prefix calendar list and --confirm is passed.
Avoid duplicate clones Partially supported Implemented by name prefix/date Probe code Uses GCClone YYYY-MM-DD prefix. Full app should persist mappings in SQLite.
Generate local FIT workout file Possible Official SDK documented; not implemented in probe Garmin FIT Python SDK SDK has an encoder. Need workout-file-specific implementation and device testing.
Copy .fit to GARMIN/NewFiles Unknown Common Garmin behavior; not verified here Not accepted as primary source Treat only as manual fallback until tested on Edge 1030.
Use GARMIN/Workouts/Schedule for date scheduling Unknown Not proven No reliable official source found Garmin Connect calendar scheduling is safer.
Coach clone marks Garmin Coach workout complete Unknown/likely unsupported Guessed Separate cloned workout A clone is a normal workout, not the original Coach workout. Completion linkage may not happen.

Official Garmin API Findings

Garmin's official Training API is aimed at approved integrations that publish workouts and training plans into Garmin Connect. The Training API page says it can publish workouts/training plans to the Garmin Connect calendar, then users sync compatible devices.

This is directionally opposite of the required first step: reading a personal Garmin Coach/adaptive workout from Garmin Connect. I found no official endpoint for reading Garmin Coach plan workout definitions from a user's account.

The Developer Program FAQ states the Garmin Connect Developer Program is for enterprise/business use and applications are reviewed. That makes it unrealistic for a local personal tool unless the user already controls an approved business integration.

Unofficial Garmin Connect Findings

The current python-garminconnect source exposes the important private endpoints:

  • Workouts: /workout-service
  • Calendar: /calendar-service
  • Training plans: /trainingplan-service/trainingplan

Relevant methods in source:

  • get_workouts(start=0, limit=100)
  • get_workout_by_id(workout_id)
  • upload_workout(workout_json)
  • upload_cycling_workout(workout)
  • schedule_workout(workout_id, date_str)
  • get_scheduled_workouts(year, month)
  • get_scheduled_workout_by_id(scheduled_workout_id)
  • delete_workout(workout_id)
  • unschedule_workout(scheduled_workout_id)
  • get_training_plans()
  • get_training_plan_by_id(plan_id)
  • get_adaptive_training_plan_by_id(plan_id)

The package demo routes plans with trainingPlanCategory == "FBT_ADAPTIVE" to the adaptive endpoint. That is the strongest lead for Garmin Coach/adaptive plan extraction, but it does not prove that cycling Coach step details are present for this user's account.

Authentication is a risk. The current python-garminconnect package has token storage and MFA support, while the older garth project now says new logins may not work because Garmin changed auth. The probe therefore treats login itself as a testable capability.

The probe also includes a read-only metadata call to /workout-service/workout/types and a normal-workout-by-ID dump. Those are intended to compare Garmin's current accepted workout schema against any Coach/adaptive JSON found during account testing.

For account testing, probe_garmin.py report --dump-json runs the read-only discovery checks in one pass and writes debug/probe_report.json. That report is the most compact evidence bundle for deciding whether the proof-of-concept can continue to clone testing.

Account Probe Evidence

The user's June 16, 2026 probe found an active Garmin Cycling Coach adaptive plan:

  • Plan ID: 45692639
  • Category: FBT_ADAPTIVE
  • Name: 2026 Fitness Plan test
  • Today's Coach workout: Sprint
  • Workout UUID: fe26ad37-69e8-495b-9d3d-2ab10fb64334
  • Estimated duration: 3120 seconds
  • Compact description: 2x13x0:10@All Out

The adaptive plan detail endpoint returned taskList[].taskWorkout summaries, not workoutSegments or workoutSteps. The calendar endpoint returned itemType=fbtAdaptiveWorkout entries with matching trainingPlanId and workoutUuid.

The missing endpoint was found in the browser network analyzer:

/gc-api/workout-service/fbt-adaptive/{workoutUuid}

For the June 16 Sprint workout, that response contains workoutSegments and nested repeat groups. Testing showed that the equivalent Connect API route also works:

/workout-service/fbt-adaptive/{workoutUuid}

The CLI now uses that Connect API route when a workoutUuid is available. The browser /gc-api route returned 403 with the package token session, so it remains a browser-specific diagnostic path.

This means exact Coach-to-normal-workout cloning is feasible with the unofficial API.

Workout Schema Findings

python-garminconnect.workout encodes the normal Garmin Connect workout schema enough for a dummy cycling workout:

  • Sport type: cycling has sportTypeId=2, sportTypeKey="cycling".
  • Step types include warmup, cooldown, interval, recovery, rest, repeat, other, and main.
  • End conditions include lap button, time, distance, calories, power, heart rate, iterations, fixed rest, fixed repetition, and reps.
  • Target types include no target, power zone, cadence, heart-rate zone, speed zone, pace zone, grade, heart-rate lap, power lap, and resistance.

The typed helpers only create simple time-based no-target steps. More exact Coach cloning will require preserving or constructing targetType, target values, and repeat groups from the raw Coach/adaptive JSON after the probe shows what Garmin returns.

Edge 1030 acceptance for target details is untested. The dummy workout intentionally starts with simple time-based no-target steps to separate "can upload/schedule/sync" from "can preserve all Coach targets".

Direct Device / FIT Fallback

A local FIT export fallback is plausible but lower priority:

  • Garmin's official FIT Python SDK can encode FIT files.
  • FIT workout files can represent structured workout concepts through FIT profile messages, but the probe does not implement this yet.
  • fitparse/fitdecode are primarily decoders and are not enough by themselves for reliable workout generation.
  • Copying files to Garmin device folders is not a good primary automation path because scheduling behavior on GARMIN/Workouts/Schedule is not reliably documented for the Edge 1030.

The safer first proof is still Garmin Connect upload + calendar scheduling. A FIT export can become a manual fallback if Garmin Connect upload/schedule works but Coach extraction or automated scheduling remains blocked.

Stop / Continue Decision

Do not build the web app yet.

Continue to Phase 2 probe because:

  • Normal workout upload/scheduling is source-supported and can be tested with a dummy workout.
  • Adaptive/Coach endpoint access is source-supported enough to probe.

Stop before Phase 3 unless the user confirms at least:

  • The dummy scheduled workout appears on the Edge 1030.
  • Garmin Coach/adaptive JSON contains enough step data to clone through the FBT adaptive endpoint, or the agreed fallback is summary-derived/manual template generation.

If the FBT adaptive endpoint changes or becomes unavailable, the best fallback architecture is:

  • Keep Garmin Connect login/calendar/workout discovery.
  • Fall back to parsing adaptive plan taskWorkout summaries and producing clearly labeled approximate normal workouts.
  • Allow manual review/edit of generated steps before upload.
  • Optionally export FIT workouts for manual device copy.
  • Do not claim unattended exact Garmin Coach cloning unless browser endpoint authentication is stable.

Sources