Routes
Send a route to your athlete's device as a course to follow, and know whether it landed.
A route is the other thing you can put on a watch: not what to do, but where. You send the GPX, TCX or FIT file, and we put it on your user's device as a course they can navigate.
{
"user_id": "4c9a7e15-6d3b-42f8-91c0-8e5b2a7d04f6",
"file": "PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPGdweCB2ZXJzaW9uPSIxLjEi…"
}You get back the route, with the name and sport we read from the file, the distance and climb we computed from it, and one entry per provider that was tried:
{
"id": "e2a47c19-3b8d-4f05-9a61-d7c3e80b5f24",
"user_id": "4c9a7e15-6d3b-42f8-91c0-8e5b2a7d04f6",
"name": "Lakeside loop",
"sport": "running",
"distance": 12430.2,
"elevation_gain": 86.0,
"points": [ "…" ],
"pushes": [
{ "provider": "garmin", "status": "synced", "external_id": "318840271" }
]
}Everything Workouts says about the push holds here too: the call is
synchronous, so pushes is what actually happened, and you only get a non-2xx when
nothing could have worked.
The file
file is the route file's bytes, base64-encoded (standard alphabet; line breaks are
ignored). The body stays JSON, so it is signed like every other call.
- GPX, TCX or FIT, up to 10 MB before encoding. We tell them apart by content, so
there is no format field. Every route planner exports at least one of them. If you only
have coordinates, write a GPX: a
<trk>with one<trkpt lat lon>per point is enough. - The file holds the route's details, and we use them: the name (GPX
<name>, a TCX course's<Name>, a FIT course's name), the description (GPX<desc>, a TCX course's<Notes>) and the sport. A FIT or TCX activity has no name in it, and neither do some GPX exports. Sendnamefor those or you get a422. - We repair what we can without guessing. A track over 10,000 points, which is
Garmin's limit on a course, is thinned evenly to fit. An elevation profile with gaps or
impossible values is dropped whole, and the route goes without elevation. A description
over 2,000 characters is cut to fit. Anything else, such as a file with fewer than two
points, is a
422that names what is wrong. distanceandelevation_gainare computed by us from the track, so the number on the watch always matches the line on the map.
Overriding the file
Everything besides file is optional, and anything you send replaces what is in the file:
| Field | |
|---|---|
name | What the athlete sees on the device. Required only when the file has none. |
description | Up to 2,000 characters, shown beside the course on Garmin. |
sport | running, trail_running, walking, hiking, cycling, road_cycling, mountain_biking or gravel_cycling. |
provider | Push to one provider instead of every connected one. POST only. |
Send sport when you know it. Files have no standard sport vocabulary, so we recognise the
common ones (Komoot's touring_bicycle is cycling) and leave the rest out. A route with
no sport shows on the device as "other".
Which providers get it
Only Garmin takes routes today, as a Course. Omit provider and it goes to Garmin if
your user has it connected; naming any other provider is a 422.
Garmin needs the athlete to have granted Course Import. That is a separate switch from
Workout Import on Garmin's consent screen, so an athlete who can receive your workouts may
still refuse your routes. Without it the push comes back failed with reason
not_permitted, and the route is still stored. When they reconnect with Course Import on,
a PATCH pushes it again.
What each status means
status | |
|---|---|
synced | On the device. external_id is Garmin's courseId. |
unsupported | This provider cannot take this route. Terminal; retrying changes nothing. |
failed | It did not land. reason says why, and whether retrying can help. |
The reason codes are the same as a workout's: not_permitted, not_connected,
unsupported and provider_error. See
Why a push did not land. There is no pending
for a route, because a Garmin course either lands or it does not.
Editing
PATCH /v1/routes/{id} takes the whole route: a file, and any overrides. An override
you leave out falls back to the new file's, not to the old route's. Garmin replaces the
course in place and keeps its courseId, so no duplicate appears on the device. Like a
workout, it re-pushes to every capable connected provider, including one your user
connected after the first push.
DELETE /v1/routes/{id} removes the course from Garmin, then from us, and answers 204
even if Garmin refuses.
Reading them back
GET /v1/routes/{id} returns the route with its points: the track as we read it from the
file, at most 10,000 of them.
{ "lat": 47.36667, "lng": 8.55, "elevation": 408 }lat and lng are degrees, WGS 84. elevation is metres above sea level, on every point
or on none. GET /v1/routes lists your routes newest first, optionally filtered by
user_id, and pages like every other list (see Paging). The listing leaves
out points to keep a page small, so fetch a single route when you need the path.
Rate limit
Route pushes share the 60 per minute device-push ceiling with workouts. It is one budget, because the provider limits us as one budget. See Rate limits.
Something wrong or missing on this page? Tell us in Discord. Need something the API doesn’t do yet? Request it on the roadmap.