Stridee
Stridee Platform Docs

Changelog

What changed in the API, the console and the docs, newest first.

Every change to the API, the console and these docs that you can see from outside — a new endpoint, a new field, a new provider, a breaking change — lands here. How we bill and how a plan is bought is not part of the API; that lives on pricing.

A date here is a release, not the day a particular commit landed — everything that shipped together is listed together. Breaking marks a change that can stop working code from working; there is a paragraph under each one saying what to do about it.

Want something that isn't here yet? Ask for it on the roadmap — it is the same board this changelog is written against.

2026-10-23

  • Apple Watch: pauses in the FIT. The FIT for an Apple Watch workout now has timer events: start when it began, stop_all and start around each pause, the athlete's or the auto-pause's (timer_trigger says which), and stop_all at the end, as a Garmin file does. A workout your user paused while doing one you pushed now also gets its lap per step; the lap a pause fell in spans it, with the pause in total_elapsed_time and not in total_timer_time. It needs the SDK's new release, which sends the pauses as events in healthkit.v1. Workouts from older releases arrive as before. Workouts has the details.

2026-10-08

  • Suunto Run: a workout push now says it cannot land. The Suunto Run and Run 2 do not run SuuntoPlus Guides, which is the only way a workout reaches a Suunto watch. Suunto accepted those pushes anyway, so they came back synced and showed in the Suunto app as "Not compatible with watch". A push for a user whose last workout was recorded on one of those watches now comes back unsupported with the reason in error, and no guide is created. Activities and wellness from those watches are unaffected. Which providers get it has the details.
  • Apple Watch: a lap per step. The FIT for a workout your user did from one you pushed now has one lap per step, each with intensity and wkt_step_index, and the plan as workout and workout_step messages, as a Garmin file does. It was a single lap before. The boundaries are placed from the plan, so a workout that was paused, cut short, or has a step ending on the lap button still arrives as one lap. When they do it has the details.

2026-10-06

  • WHOOP is the ninth provider. POST /v1/connect accepts whoop, and it covers every strap that syncs to the WHOOP app. Finished workouts arrive as activity.created once WHOOP has scored them. WHOOP exports no workout file, so the FIT behind file.url is one we write from its summary: sport, times, heart rate, energy and time in each heart-rate zone, with no GPS track and no per-second samples. sport is on our vocabulary and name is WHOOP's own sport name. Sleep, recovery, the day's strain and weight arrive as wellness.created (sleep, hrv, daily, body_composition), with the recovery score and day Strain in summary. Connecting replays the last five years as ordinary deliveries. No workout push: WHOOP has no API to receive one. What each provider sends and the support matrix have the details.

  • Traditional Chinese on the pages we host. The consent screen, the confirmation after it and the manage page are now also written in Traditional Chinese — zh-Hant as lang on POST /v1/connect and POST /v1/connections/manage-link. zh-TW, zh-HK and zh-MO are read as Traditional too; every other zh tag, a bare zh included, is still Simplified. One thing to know: a user whose browser or lang says Taiwan, Hong Kong or Macau now sees Traditional where they saw Simplified before. Language has the details.

2026-10-05

  • Suunto is the eighth provider. POST /v1/connect accepts suunto, and it covers every watch that syncs to the Suunto app. Finished workouts arrive as activity.created with Suunto's FIT file. Sleep, overnight HRV, daily totals and recovery arrive as wellness.created (sleep, hrv, daily, stress). Connecting replays five years of workouts and a year of wellness as ordinary deliveries. POST /v1/workouts reaches Suunto as provider: "suunto": the workout becomes a SuuntoPlus Guide, which your user picks on the watch before starting. What each provider sends and which providers get a workout have the details.

2026-10-03

  • Chinese, Korean and Japanese on the pages we host. The consent screen, the confirmation after it and the manage page are now also written in Simplified Chinese, Korean and Japanese — zh, ko and ja as lang on POST /v1/connect and POST /v1/connections/manage-link, with or without a region. Any zh tag, zh-TW included, gets Simplified. As with the first nine, a user whose browser is set to one of these now sees it where they saw English before. Language has the details.

2026-10-02

  • Workout push to Apple Watch. POST /v1/workouts now reaches Apple Watch, as provider: "apple". Apple has no server API for it, so the last step runs in your iOS app: set schedulesWorkouts: true in the StrideeHealth SDK, call requestWorkoutSchedulingAuthorization() when the user turns it on, and the SDK schedules what you push with WorkoutKit. The push is pending until the user's phone syncs, then synced; edits and deletes follow at the next sync. Nothing changes for an app that doesn't turn it on — an Apple connection is a workout target only once its scope shows workout_scheduling. Apple carries time, distance, calorie and open steps, pace, heart rate, power and cadence targets, heart-rate and power zones, and repeats; it needs a scheduled_date, and anything it can't express comes back unsupported. And when the user does a workout you pushed, its activity.created now says so in data.workout_id — also on GET /v1/activities. A device token can now read one thing: the workouts scheduled for its own user's watch. Workouts on Apple Watch has the setup.
  • Apple Health. Apple Watch and iPhone data now reaches you from HealthKit, pushed by your own iOS app with the new StrideeHealth Swift package (iOS 17+). Your server mints a one-hour device token for the signed-in user with POST /v1/device-tokens; the app uses it on /v1/device/* to connect the user and upload, and can push for that one user only and read nothing. Workouts arrive as activity.created with provider: "apple" and a FIT file we encode from HealthKit's samples; wellness arrives as nine kinds — daily, sleep, hrv, fitness, respiration, pulse_ox, body_composition, blood_pressure and skin_temperature. Apple's HRV is SDNN and lands in hrv_sdrr, not hrv_avg. Apps that read HealthKit themselves can relay the same JSON from their backend with POST /v1/accounts/{id}/apple/workouts and /apple/wellness. POST /v1/connect with apple stays a 400, now pointing at device tokens, and disconnecting an Apple connection also revokes that user's device tokens. The package also has the pieces for asking well: HealthAccessPrimer, a SwiftUI screen to show before Apple's one-time permission sheet; requestAuthorization() to ask without connecting; and status.authorization plus status.lastDataFound, which together are how you notice a user who left the switches off — iOS never reports a denied read. Apple Health has the setup.
  • The pages we host now speak nine languages. The consent screen, the confirmation after it and the manage page your users disconnect from are available in English, German, Danish, Norwegian, Swedish, Polish, Czech, Slovak and Spanish. They follow your user's browser language on their own, so there is nothing to change. To choose instead, pass lang on POST /v1/connect or POST /v1/connections/manage-link — de, da, nb, sv, pl, cs, sk, es or en, with or without a region — and it wins over the browser. A language we don't have yet falls back rather than failing. One thing to know: a user whose browser is set to one of these languages now sees it where they saw English before. Language has the details.

2026-09-28

  • Return URIs for mobile apps. A return URI can now be your app's own scheme in reverse-domain form, such as com.yourapp.ios:/stridee-callback, as well as an https:// URL. Register it under Return URIs or with POST /platform/redirect-uris, and pass it as return_uri on POST /v1/connect so the browser sheet you opened consent in closes back into your app. Mobile apps has the details.

2026-09-26

  • Your colours on the hosted pages. The pages your athletes see — consent, connected, declined, failed, manage and link expired — are redesigned, and take your brand: a page background, a button colour and a corner shape, from GET/PUT /platform/brand or the Branding screen. Text, card and glow follow from the background you choose. The screen previews every page, on a phone or a desktop, as you type. Nothing changes for an account that sets nothing.

2026-09-25

  • Choosing events per endpoint. A webhook endpoint can now receive a subset of events. Send enabled_events with exact types or family wildcards such as wellness.* on POST or PATCH /platform/webhooks, or choose them on the Webhooks screen. Existing endpoints stay on ["*"], so nothing you receive changes. ping and account.deleted are delivered whatever you subscribe to. Choosing events has the details.

2026-09-24

  • Routes. POST /v1/routes puts a route on your user's Garmin as a Course: send the GPX, TCX or FIT file (base64), and we read its name, description, sport and track, and compute the distance and climb. Send name, description or sport to override the file. A track over 10,000 points is thinned to fit. GET, PATCH and DELETE /v1/routes/{id} and a paged GET /v1/routes work the same way as workouts, and each push carries the same status and reason codes. Garmin needs the athlete's Course Import permission. Route and workout pushes share the 60-per-minute device-push limit. /v1/routes takes bodies up to about 14 MB for the file; every other signed call keeps its 1 MiB limit. The console has a new Routes screen. Routes has the details.

2026-09-23

  • Strength workouts. POST /v1/workouts accepts "sport": "strength" and a new exercise step, with name, sets, reps, and optional weight_kg, rest_seconds and note. Exercises inside a repeat make a circuit. Garmin gets each exercise as sets of reps with the load and rest, matched to Garmin's exercise library where we can. COROS and Wahoo can't carry a load, so a workout with an exercise comes back unsupported from both. reps durations are now accepted on strength as well as cardio and hiit. Workouts has the format.

2026-09-19

  • Zepp publishes overnight HRV. An Amazfit night's HRV now arrives as a wellness.created event of kind hrv, with the night's average in hrv_avg. It is Garmin's HRV summary field for field, so code that already reads Garmin HRV reads this with no branch. Zepp sends no per-reading values: its hrvValues is null, and so is that key behind series_url. Wellness has the per-provider shape.

2026-09-18

  • Garmin wellness history now backfills on connect. The wellness docs have said history on connect applies to wellness as well as activities. On Garmin it did not: a user who granted historical export got years of activities and no wellness history at all. We now request Garmin's Health API history for every summary type except epoch — sleep, hrv, daily, stress, respiration, pulse_ox, fitness, body_composition, blood_pressure, skin_temperature and health_snapshot. Garmin serves only one month of wellness history, against years of activities, so expect about the last four weeks. Each summary arrives as an ordinary wellness.created, with its own old calendarDate. The user needs both historical and health export granted. Users who connected before today are backfilled in the background over the coming days, with no reconnect needed.

  • Fitbit is the seventh provider. POST /v1/connect accepts fitbit, and it covers Pixel Watch too. It is built on the Google Health API, not the Fitbit Web API that Google is retiring, so a user who linked Fitbit elsewhere through the old API consents again here. Finished workouts arrive as activity.created with a TCX file — Google exports no FIT. Sleep, resting heart rate, HRV, breathing rate and SpO₂ arrive as wellness.created (sleep, daily, hrv, respiration, pulse_ox). Partial consent is honoured: each kind is read only if the user granted it, and scope on the connection says what they granted. No workout push, and no history on connect yet. The support matrix has the current shape.

2026-09-14

  • The Stridee CLI: real deliveries on localhost, with no tunnel. stridee listen --forward-to localhost:3000/webhooks opens an outbound connection to us and acts as one more webhook endpoint on your account for as long as it runs. Every event your account emits is POSTed to your local server exactly as production gets it: the same sealed envelope, the same webhook-id, webhook-timestamp and webhook-signature headers. The CLI never decrypts anything, so the code under test is your handler's own verify and decrypt. Each delivery prints one line saying what your handler answered and whether it echoed the nonce, and shows up in the console's delivery log like any other.

    stridee login has no token. The CLI generates an Ed25519 keypair, sends only the public half, and approving the code in the console registers it as a signing key. It shows under Keys as Made by the Stridee CLI, and stridee logout revokes it. stridee trigger ping pings running listeners only, never production endpoints. Listening does not take deliveries away from your production endpoints: the listener gets a copy. Listeners do not count toward your endpoint limit, and an account can run up to five at once.

    Install it with curl -fsSL https://download.stridee.com/install.sh | sh — macOS and Linux, Intel and ARM, one static binary and nothing else left on the machine. Every command, with its options and examples, is in the CLI reference, generated from the CLI's own definitions so it matches what stridee <command> --help prints.

  • Workout pushes say why they did not land. Each entry in pushes[] now carries reason, a stable code to branch on, next to error, which is still a sentence you should not parse. failed covered three problems with three different fixes, and reason separates them: not_permitted (the athlete connected but did not grant workout access, so they have to reconnect, and retrying won't help until they do), not_connected, and provider_error (the only one worth a PATCH). unsupported and schedule_failed complete the set. A clean push has no reason at all. Pushes made before this release have been backfilled.

    A Garmin push that Garmin answers with 401 or 403 is now reported as not_permitted instead of a provider refusal. That means the athlete switched Workout Import off in Garmin Connect before we heard about it. The error sentences for Garmin and Wahoo now say what to ask the athlete to do. The console's Workouts screen shows the reason under each push. Workouts has the full table.

  • COROS takes undated workouts. A workout with no scheduled_date used to come back unsupported for COROS, because the COROS calendar had nowhere to put it. It now goes to the athlete's COROS workout library and reports synced. A dated workout still has to fall between today and a year ahead.

2026-09-09

  • Zepp publishes wellness — sleep, and sleep only. An Amazfit night now arrives as a wellness.created event with the stage breakdown, Zepp's own sleep score, finality and the per-stage timeline behind series_url. Zepp's health API mirrors Garmin's field for field, so code that already reads Garmin sleep reads this with no branch, and zepp is now a value the provider filter on GET /v1/wellness accepts.

    There is no daily from a Zepp watch — steps, calories and resting heart rate are not on their health push. Overnight HRV is on it and is deliberately not published: Zepp documents no schema for it, and we would rather serve nothing than a resting-HRV number nobody has verified. Wellness has the per-provider shape.

  • COROS history on connect now reaches your event stream. The walk existed before but wrote first-party rows only; it delivers through the same fan-out as live activities now, so past workouts arrive as ordinary activity.created events with nothing marking them as historical.

    How deep is the athlete's choice, not ours. COROS's consent screen carries a checkbox offering access to their previous workouts — left alone it grants 90 days, ticked it grants their whole history, and we walk 30-day windows back until COROS answers 5011. Nothing in the API says which grant you got, so we cannot tell you in advance how far a given user goes. Two caveats, both COROS's: the deep grant expires 24 hours after binding, so history is collected at once rather than offered as a setting later, and COROS serves only what was recorded on a COROS device — a FIT imported through Training Hub is never listed. Provider support & limits has both in full.

  • Wahoo connections replay history, up to five years of it. Connecting a Wahoo athlete now delivers their past workouts as ordinary activity.created events — same shape, same file block, nothing marking them as historical. It was the one connectable provider that started at the athlete's next activity.

    Wahoo publishes no backfill endpoint, so this is a pull rather than a replay, and their rate limit is per application rather than per athlete. A heavy athlete's full history can therefore take up to a few days to finish arriving; recent workouts come first, and live ones are never delayed behind it. Provider support & limits has the current depth and the caveat in full.

2026-09-06

  • Hammerhead is the sixth provider. POST /v1/connect accepts it and finished rides arrive as activity.created like any other. It has no wellness surface to publish — it is a bike computer — and workout push is not built against it yet, so naming it on POST /v1/workouts is a 422. The support matrix has the current shape.
  • Provider support & limits is now a page: /docs/providers. It says, per provider, which endpoints work, how far back history reaches and what is missing. It replaces asking in Discord, which was the way to find this out before.

2026-08-23

  • Zepp is a connectable provider.

  • The platform moved to its own host, platform.stridee.com. The docs, the console and this page all live here now, and stridee.com/developer/* is no longer the address.

    Breaking, for bookmarks and links only. The API host is unchanged — nothing your code calls has moved. Update links you have published; the old paths do not serve the console.

2026-08-16

  • Your name and logo on the consent screen. Set them on Branding and the page an athlete sees when they connect a watch says who they are connecting it to. The name and the logo are shown together or not at all.

2026-08-09

The first public shape of the platform.

  • GET /v1/activities, across every connected provider, in one normalized shape.
  • Encrypted deliveries. Every webhook body is sealed to your public key as a JWE and signed with a detached JWS you verify against our published JWKS. See Encrypted deliveries and Verifying a delivery.
  • Request logs, on Logs — every signed call to /v1/*, including the ones we refused. A 401 you cannot explain from your side is a row you can read from ours.
  • Connected accounts, on Accounts, keyed by your own external_user_id.
  • Resending a delivery, per delivery, from the endpoint that received it. Nothing retries automatically — a person presses a button. Design your outage handling for that.
  • Registered redirect URIs, on Redirect URIs. POST /v1/connect will only return to a URL on that list.
  • Every platform id is a UUID v4 — key ids, webhook ids, event ids, delivery ids. They are opaque: nothing in the string tells you what it names, so store them as given, compare them whole, and keep the id next to the thing it belongs to.

Something wrong or missing on this page? Tell us in Discord. Need something the API doesn’t do yet? Request it on the roadmap.