Export API¶
GET /export/sync returns a small JSON document for other services that act on what the mirror knows: the starred routes, and the activities that started since a given time. It is computed live from the mirror on every request, so a consumer sees a change as soon as the sync has it, without waiting for the next snapshot. It costs no Strava request and is a few kilobytes.
The first consumer is garmin-mcp's Strava sync, which renames Garmin activities after their Strava names and deletes the courses Strava made for routes that are no longer starred. Anything else may read it; the contract below is stable within a major version.
Request¶
| Path | /export/sync. /export/garmin-sync is an alias (the name in 4.2) and answers identically. |
| Authentication | The SNAPSHOT_TOKEN bearer, the same token that serves /snapshot.duckdb. With SNAPSHOT_TOKEN unset the endpoint answers 404; with a wrong token, 401. |
since |
Optional. An ISO 8601 date or timestamp (2026-10-01, 2026-10-01T06:00:00Z); anything new Date() parses. Activities that started at or after it are returned, in chronological order. Without since, activities is empty. An unparseable value answers 400. |
| Transport | Only over the HTTP transport (MCP_TRANSPORT=http). With the oauth profile the gateway does not proxy this path; call the server's own port, as the snapshot. |
Response¶
200 with Content-Type: application/json:
{
"generated_at": "2026-10-25T06:12:40.118Z",
"route_list_checked_at": "2026-10-25T06:00:03.512Z",
"activity_list_checked_at": "2026-10-25T06:10:11.907Z",
"starred_routes": [
{
"id": "3302612345678901234",
"name": "Sunday loop",
"distance": 64210.5,
"start": [47.3769, 8.5417]
}
],
"activities": [
{
"id": "16012345678",
"name": "Coffee ride",
"start_date": "2026-10-24T07:02:15.000Z",
"elapsed_time": 5421
}
]
}
| Field | Type | Meaning |
|---|---|---|
generated_at |
string, ISO 8601 UTC | When this response was computed. |
route_list_checked_at |
string or null | When the route list, and so the starred flags, was last fetched from Strava. null before the first fetch. |
activity_list_checked_at |
string or null | When the recent activity list was last fetched. Webhook events update individual activities between listings, so an activity can be newer than this. null before the first listing. |
starred_routes |
array | Every route of the athlete with starred true in the mirror, ordered by id. Unstarred and deleted routes are absent. |
starred_routes[].id |
string | The route id, as a string: route ids exceed 2^53 and would lose precision as a JSON number in most parsers. Compare it as text. |
starred_routes[].name |
string | The route's name; an empty string when Strava reported none. |
starred_routes[].distance |
number or null | Length in meters. |
starred_routes[].start |
[lat, lng] or null |
First point of the route's summary polyline, decimal degrees; null for a route without a polyline. |
activities |
array | Activities not deleted on Strava whose start_date is at or after since, oldest first. Empty without since. |
activities[].id |
string | The activity id, as a string, for the same reason as routes. |
activities[].name |
string | The activity's current name in the mirror. |
activities[].start_date |
string, ISO 8601 UTC | Start time in UTC (Strava's start_date, not the local wall-clock start_date_local). |
activities[].elapsed_time |
number or null | Elapsed seconds. |
Freshness¶
The response reflects the mirror, not Strava, at the moment of the request:
- Names and new activities arrive through the webhook within seconds when
STRAVA_WEBHOOK_URLis set, otherwise through the activity-list poll every 15 minutes.activity_list_checked_atsays when that poll last ran. - Starred flags come only from the route-list poll, since Strava sends no event for starring or unstarring: every
STRAVA_ROUTES_INTERVAL_MINUTES(an hour by default, five at the lowest).route_list_checked_atsays when it last ran. A consumer that deletes things based on the starred flag should lower the interval and should treat an oldroute_list_checked_atas a reason to wait, not to act. - Deleted activities disappear from
activitiesas soon as the mirror learns of the deletion (webhook event, or the daily 90-day relisting and the weekly full relisting).
Example¶
curl -s -H "Authorization: Bearer $SNAPSHOT_TOKEN" \
'http://<host>:8000/export/sync?since=2026-10-01T00:00:00Z' | jq '.activities[] | {id, name}'
Compatibility¶
Fields are only added, never renamed or removed, within a major version; a consumer should ignore fields it does not know. The /export/garmin-sync alias stays until the next major version.