Stridee
Stridee Platform Docs

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.

POST /v1/routes
{
  "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:

JSON
{
  "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. Send name for those or you get a 422.
  • 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 422 that names what is wrong.
  • distance and elevation_gain are 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
nameWhat the athlete sees on the device. Required only when the file has none.
descriptionUp to 2,000 characters, shown beside the course on Garmin.
sportrunning, trail_running, walking, hiking, cycling, road_cycling, mountain_biking or gravel_cycling.
providerPush 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
syncedOn the device. external_id is Garmin's courseId.
unsupportedThis provider cannot take this route. Terminal; retrying changes nothing.
failedIt 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.

JSON
{ "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.