A training plan is more useful on the wrist than in an app. If you build a coaching product, a training planner or a running club tool, the feature your users ask for is "send it to my Garmin": intervals with pace targets that the watch walks them through, on the right day.
This guide shows how to do that with one API call.
## What you would otherwise need
Garmin's Training API is what puts workouts on a watch. It sits behind the Garmin Connect Developer Program, which [is not taking new applications](https://stridee.com/blog/garmin-developer-program-paused). With access, you would still write the encoder from your workout model to Garmin's step format, schedule each workout on the calendar as a second call, and handle the cases where Garmin rejects a step.
With the [Stridee Platform](https://platform.stridee.com) the approval is already in place and the encoder is ours. Workout push is included on every plan.
## 1. Connect the athlete
A workout goes to a user who has linked their Garmin account. That is one call to `POST /v1/connect` and one redirect, covered in [How to integrate Garmin data into your app](https://stridee.com/blog/how-to-integrate-garmin-api).
One thing matters here: on Garmin's consent screen the athlete has to leave **Workout Import** switched on. Without it the connection works for reading data and every push fails.
## 2. Send the workout
Describe the session as a list of steps:
```json
{
"user_id": "4c9a7e15-6d3b-42f8-91c0-8e5b2a7d04f6",
"scheduled_date": "2026-10-14",
"name": "5 × 1km @ threshold",
"sport": "running",
"steps": [
{ "type": "warmup", "duration": { "type": "time", "seconds": 600 } },
{
"type": "repeat",
"repeats": 5,
"steps": [
{
"type": "active",
"duration": { "type": "distance", "meters": 1000 },
"target": { "type": "pace", "low": 3.5, "high": 3.8 }
},
{ "type": "rest", "duration": { "type": "time", "seconds": 90 } }
]
},
{ "type": "cooldown", "duration": { "type": "open" } }
]
}
```
Send it as a signed `POST /v1/workouts`. It appears in Garmin Connect on the scheduled date and syncs to the watch.
The rules that trip people up:
- **Durations are seconds and metres**, not minutes and kilometres.
- **Pace is minutes per kilometre**, and `low` is the faster end, so the smaller number.
- **`open` runs until the athlete presses lap.**
- **A `repeat` cannot contain another `repeat`.** Watches render one level, and we refuse the workout instead of silently flattening it.
## 3. Read the result
The call is synchronous. It returns once Garmin has answered, usually in under a second, so there is no job to poll:
```json
{
"id": "0f31a8c4-59d2-4e07-b6a1-8c74e2f95d30",
"name": "5 × 1km @ threshold",
"scheduled_date": "2026-10-14",
"pushes": [
{ "provider": "garmin", "status": "synced", "external_id": "1284410973" }
]
}
```
| `status` | Meaning |
| --- | --- |
| `synced` | It is on the watch. `external_id` is Garmin's id for it |
| `pending` | Accepted, not on the device yet. Nothing for you to do |
| `unsupported` | This provider cannot represent this workout. Retrying changes nothing |
| `failed` | It did not land. `reason` says why |
A failed push carries a stable `reason` to branch on. The one you will see most with Garmin:
```json
{
"provider": "garmin",
"status": "failed",
"reason": "not_permitted",
"error": "the athlete has not granted Garmin's Workout Import permission — ask them to reconnect Garmin and leave Workout Import switched on"
}
```
Show that to the user as "reconnect Garmin and allow workouts" and send them through `POST /v1/connect` again. Retrying before they do fails the same way.
## Targets
A step can hold a target, and a second one alongside it:
```json
{ "type": "pace", "low": 3.5, "high": 3.8 }
{ "type": "heart_rate", "low": 150, "high": 165 }
{ "type": "heart_rate_zone", "zone": 3 }
{ "type": "power", "low": 240, "high": 260 }
{ "type": "power_percent", "low": 95, "high": 105 }
{ "type": "cadence", "low": 85, "high": 95 }
```
Zones are the athlete's own, as set on their device. `power_percent` is percent of FTP, and `heart_rate_percent` is percent of maximum heart rate.
Steps can also end on a condition instead of a clock, which is how you write "recover until 130":
```json
{ "type": "recovery", "duration": { "type": "heart_rate_below", "bpm": 130 } }
```
## Strength sessions
Garmin is the only provider that takes strength workouts. Write them as exercises:
```json
{
"user_id": "4c9a7e15-6d3b-42f8-91c0-8e5b2a7d04f6",
"scheduled_date": "2026-10-15",
"name": "Lower body A",
"sport": "strength",
"steps": [
{ "type": "exercise", "name": "Barbell back squat", "sets": 4, "reps": 8, "weight_kg": 80, "rest_seconds": 120 },
{ "type": "exercise", "name": "Romanian deadlift", "sets": 3, "reps": 10, "weight_kg": 60, "rest_seconds": 90 }
]
}
```
We match `name` against Garmin's exercise library, so the watch shows Garmin's own exercise and logs the sets under it. Write names the way a gym would. A name we cannot match still arrives, as a reps step labelled with your text.
## Editing and deleting
`PATCH /v1/workouts/{id}` takes the whole workout and pushes it again. `DELETE /v1/workouts/{id}` removes it from the watch and then from us. When the athlete changes their plan in your app, their watch follows.
## More than Garmin
The same call reaches COROS, Wahoo and Apple Watch. Leave `provider` out and the workout goes to every device the user has connected, with one entry in `pushes` for each:
```json
"pushes": [
{ "provider": "garmin", "status": "synced", "external_id": "1284410973" },
{ "provider": "wahoo", "status": "unsupported",
"error": "Wahoo structured plans cover running and cycling, not 'swimming'" }
]
```
A partial success is still a `201`. You only get an error when nothing could have worked.
## Routes too
A workout says what to do. A route says where. `POST /v1/routes` takes a GPX, TCX or FIT file and puts it on the watch as a course the athlete can follow. See the [routes docs](https://platform.stridee.com/docs/routes).
## Next
- [Workouts reference](https://platform.stridee.com/docs/workouts): swimming, multi-sport, limits and every step type
- [Quickstart](https://platform.stridee.com/docs/quickstart)
- [Garmin integration](https://platform.stridee.com/integrations/garmin)
- [Pricing](https://platform.stridee.com/pricing): 14-day free trial on every plan
How to send structured workouts to a Garmin watch from your app
Put intervals, pace targets and strength sessions on your athletes' Garmin watches with one API call, and know whether each one landed.