One API for every wearable.Read and write.
Garmin, COROS, Polar, Wahoo, Apple Watch and Strava — normalized into one FIT file, so you interpret one format instead of six APIs. Then push structured workouts straight back to the athlete’s watch.
The private beta is open. Three days free, then $20 a month — checkout creates your account and switches the console on. Only 50 slots. Or walk through the console first.
link one of your users — no provider credential involved
// Your own id for your own user. No Stridee account needed.
const link = await stridee.post('/v1/connect', {
provider: 'coros',
external_user_id: 'user_4821',
return_uri: 'https://app.yourapp.com/settings/devices',
});
// Redirect their browser. Good for 30 minutes.
res.redirect(link.connect_url);Your users can connect these today

Coverage
What works, and what doesn’t yet
Coverage differs because provider APIs differ. Where a provider doesn’t send something, the field is absent rather than estimated — a null you can branch on is worth more than a number nobody measured. Nothing below is marked live unless an endpoint serves it today.
| Provider | Connect | Activities | Daily summaries | Workout push |
|---|---|---|---|---|
COROSPACE, APEX, VERTIX | ||||
StravaHistory import |
Wahoo is the one provider that reports what the athlete actually granted, so a connection’s scope comes back verbatim. COROS and Polar don’t, and we leave the field null rather than filling it with the scopes we requested.
Apple Watch and Strava aren’t connectable through the API yet — HealthKit is on-device by design, and Strava history import runs in the Stridee apps rather than behind /v1/connect. Naming either in a connect call is a 400 today, and both are on the roadmap below.
Per-provider detail
What each integration delivers, which devices it covers, and the provider API you aren’t writing against.
Next on the roadmap
Not shipped yet. The founding 50 decide the order — say which one you need and it moves up.
Connections
Three calls, and none of them is an OAuth flow you built.
The version you’d build yourself starts with a partner application, waits weeks for approval, and ends with a client secret in your environment, a PKCE verifier parked somewhere both halves of the flow can reach, and a callback in your router — repeated per provider, each with its own quirks. The registration here is ours: one client per provider, one callback, one consent screen we host.
Register where they come back to
One HTTPS return URI in the console, matched as a prefix. We won’t send your users to a URL you haven’t claimed — an endpoint that redirects a browser wherever a caller says is a phishing link wearing our domain.
Send us your id for them
Whatever your database already calls them. You get back a URL to redirect their browser to, and a user_id you can keep or throw away. Call it twice with the same id and you get the same person back, which is what makes retrying safe.
Their activities start arriving
account.connected tells your backend the link exists — independently of the redirect, so a user who closes the tab mid-flow still connected. Every finished workout after that lands as activity.created.
The differences stay on our side
One provider issues a refresh token, one rotates both every two hours, one issues neither and never expires, one requires PKCE and reports what the athlete granted through a separate call that can change afterwards. None of that reaches your integration — and we never hand you a provider’s own id for an athlete, so you and another developer serving the same person can’t join your user tables against each other.
POST /v1/connect
POST /v1/connect HTTP/1.1
Host: api.stridee.fit
Content-Type: application/json
{
"provider": "coros",
"external_user_id": "user_4821",
"return_uri": "https://app.yourapp.com/settings/devices"
}Credentials
Nothing here is a secret you have to keep.
No API key on the way in, no shared webhook secret on the way back. Both directions are keypairs: you sign requests with a private key that never leaves your machine, and we seal deliveries to a public key whose private half never reaches us.
There is no API key
Requests carry an Ed25519 signature over the request itself — RFC 9421 HTTP Message Signatures — made with a private key that never leaves your machine. A bearer token is a thing that can rest in a log line, a proxy, a backup or a CI variable. A signature never travels, so there is nothing in any of those places to steal.
Every delivery is sealed to your key
JWE compact, ECDH-ES over X25519 with A256GCM. You hold the private half; we hold the public one. HTTPS protects a body as far as whatever terminates TLS — usually a CDN or a logging sidecar — and past that hop the plaintext exists in exactly two places, ours and yours. There is no cleartext mode and no shared secret to rotate.
And signed with ours
Encryption answers who may read a delivery. It cannot answer who sent it, because sealing a body to a public key is what a public key lets anyone do. So every delivery also carries a detached JWS you verify against the JWK Set we publish — fetch it, don’t paste it, and our rotation never becomes your redeploy.
Echo the nonce back
One line, and it buys what a status code cannot. A fresh nonce rides inside each ciphertext, and only something holding your private key can return it. A lapsed domain, an undeployed key, a proxy answering in front of your service — all of them return a healthy 200, and all of them stop echoing.
And you can prove it before a user exists
Send ping mints a real event, seals it to your key and POSTs it to your URL — the same path a real delivery takes, so a green one means something. It reports two answers, not one: that your endpoint replied, and that it could actually open the body.
RFC 9421 — there is no API key to send
POST /v1/connect HTTP/1.1
Host: api.stridee.fit
Content-Digest: sha-256=:zeuXewdQlhgzXOqle0t2/j7Jxy9QEC903PurkiKOxvs=:
Signature-Input: sig1=("@method" "@target-uri" "content-digest");created=1770124811;keyid="5a8f31d6-0c94-4b27-a3e5-71fd2809bc4e";nonce="g1UIt9b3k_FkSpsX2KVCGA";alg="ed25519"
Signature: sig1=:wqcAqbmYJ2ji2glfAMaRy4gruYYnx2nEFN2HN6jrnDnQ…:
# The key never travels. There is nothing in a log to steal.Three days free. Then $20 a month.
The private beta is open, and checkout is the whole signup — pay and your account exists, with the console switched on, before you have finished reading the receipt. No waitlist, no sales call, and nobody approving you.
- Keys the moment checkout finishes — no waitlist, no approval
- Garmin, COROS, Polar and Wahoo behind one connect call
- $20/month for the whole API — every provider, every endpoint
- One of only 50 founding slots, and the price they keep
- A private Discord channel with the engineers who wrote the integrations
- A vote on what we build next — the founding 50 order the roadmap
Cancel in one click from the billing portal, at any point in the three days or any month after. If something goes wrong on our side, email hi@stridee.fit and we refund you. No forms, no conditions.
Founding slots
50
Only 50 founding slots exist. When they are gone, they are gone.
Card required, nothing charged for three days. Flat rate for the whole API, not per connected athlete.
Start building — free for 3 daysStripe checkout · cancel any time · keys the moment it clears
What happens next
You pay
Stripe collects the card. Three days free first, then $20/month, and cancelling is a button in the same billing portal.
We create your account
Against the email you paid with, automatically, the moment Stripe confirms. Nobody reviews it.
You log in
platform.stridee.fit/login, same email, code in your inbox. Mint a signing key and your first call works.
Already bought and want the console? platform.stridee.fit/login
Not ready to claim?
Come talk to us in Discord first. We answer questions there before anywhere else.
Join the DiscordBefore you claim
Checkout is the signup. You pay on Stripe with whatever email you want the account under, and that purchase creates the account and opens the developer console on it — automatically, in the seconds it takes Stripe to tell us. Then go to platform.stridee.fit/login, enter the same address, and the code we email you signs you in. Nobody reviews an application, because there is no application.
Three days free, then $20 a month, flat, for the whole API — every provider, every endpoint. Flat rather than priced per connected athlete, and no contract: cancel from the Stripe billing portal at any point in the trial or any month after, and access ends when the period does.
Everything on this page that is marked live. Your users connect Garmin, COROS, Polar and Wahoo through a hosted consent flow you did not build, finished activities arrive as encrypted webhook deliveries, and the console gives you signing keys, webhook endpoints, an event stream, the accounts you have connected and a log of every signed request — including the ones we refused, which are the ones worth debugging. What is not live is marked so below, in the coverage table and the answers underneath it.
Garmin, COROS, Polar and Wahoo. Those are the four a POST /v1/connect call can name — anything else comes back as a 400, because the path exists and the argument does not. Apple Watch and Strava carry wordmarks across the product and sync inside the Stridee apps, but they have no connect path behind the API yet; both are on the roadmap, and founding partners decide the order.
No, and that is deliberate. Requests are authenticated with an Ed25519 signature over the request itself — RFC 9421 HTTP Message Signatures — using a key that never leaves your machine. Every request names your key id, covers the method, the URL and a digest of the body, and carries a fresh nonce we remember for five minutes so a captured request cannot be replayed. GET /v1/whoami exists to prove your client signs correctly before you call anything real.
An activity.created event carrying the provider’s own summary — sport, start time, device and the provider’s activity id — sealed to your key. The FIT file is not in it: it is a pre-signed provider URL that expires, and re-hosting your athletes’ activity files is a storage and retention decision rather than something to slip into a webhook payload. There is no canonical re-encoded FIT today, and no laps or per-second stream endpoint yet.
Not yet. workout.pushed and workout.completed are designed and documented, and nothing emits them today — there is no workouts endpoint in the shipped API. It is the thing we most want to get right, and the founding fifty are who we are building it with. Nothing on this page counts it as live.
Four things, in this order, and each is cheaper than the one after it. Verify the detached JWS against our published JWKS before you parse anything. Check the delivery is recent and that you have not seen its webhook-id before — retries reuse it. Decrypt the JWE with your private key and check the user_id inside is yours, since a webhook URL is a public endpoint and anyone can seal a body to a public key. Then return a 2xx carrying the nonce you just decrypted. Decrypt in the request path, before you ack — you cannot enqueue the raw bytes and return 200 blind.
Any 2xx is an ack. Non-2xx responses and timeouts are retried with exponential backoff, and sustained failures mark the endpoint degraded and then failing in the console — deliveries are queued while you fix it, not dropped. Every event is kept for 30 days, and each delivery records the key it was sealed to, so a replay after a rotation is still decryptable with the key it was actually encrypted to.
The full documentation is public and needs no account. Still have questions? Ask us in Discord
