Stridee
Stridee Docs

Connect a device

Link one of your users to their watch, without holding a provider client secret or implementing a callback.

Your user has a Garmin watch and you want their workouts. Built yourself, that is a partner application, weeks of approval, a client secret, a PKCE verifier and an OAuth callback — per provider, each with its own quirks.

Here it is three calls, and none of them involve a provider credential. The registration is ours: one client per provider, one callback URL, one consent screen. What you get back is your user, linked.

Who's who

Three parties, and keeping them apart is most of understanding this page.

YouThe developer. Your Stridee account, your keys, your webhook endpoints.
Your userAn athlete who signed up for your product. They have no Stridee account and will never be asked for one.
The connectionOne of your users' authorization at one provider.

You refer to your users by your own id — whatever your database already calls them. That is the only identifier you send and the only one you have to store. We mint a user_id and hand it back, but nothing breaks if you throw it away: it is on every connection you list, and GET /v1/accounts looks it up from your id.

1. Register a return URI

Where your user's browser should land once they have finished consenting. Add it under Return URIs in the console before your first connect call.

It is matched as a prefix, so register the stem once and vary the rest per user:

Text
https://app.yourapp.com/settings/devices

HTTPS only, no wildcards, and it has to name a host — we won't send your users to a URL you haven't claimed. Skip this step and your users finish on a plain page we host that says the connection worked: fine for a first try, not for production.

Signed with your Ed25519 key — see /docs/signing
POST /v1/connect HTTP/1.1
Host: api.stridee.com
Content-Type: application/json

{
  "provider": "coros",
  "external_user_id": "user_4821",
  "return_uri": "https://app.yourapp.com/settings/devices"
}
200
{
  "connect_url": "https://api.stridee.com/connections/start?state=…",
  "user_id": "4c9a7e15-6d3b-42f8-91c0-8e5b2a7d04f6",
  "expires_at": "2026-08-05T11:44:02Z"
}

Redirect your user's browser to connect_url. It is a screen a person has to read — don't fetch it server-side, and don't cache it. It stops working after 30 minutes, matching how long the provider's own authorization code stays good.

connect_url is a page we host, showing your logo beside ours and carrying our terms; one click later your user is at the provider. That click is required and there is no setting to skip it: your user is about to send their heart rate, sleep and GPS traces through infrastructure they have never heard of, and every provider agreement behind this API assumes they were told who receives it. Set the name and logo they see under Branding — it previews the real page as you type.

Calling this twice for the same external_user_id returns the same user_id rather than a second identity, so retrying is safe. provider is any of the seven on Provider support & limits; anything else is a 400. Everything below is the same whichever you name — the differences in how each provider handles tokens and consent are ours to carry.

3. Your user comes back

They approve, and we send them to your return_uri with two parameters appended:

Text
https://app.yourapp.com/settings/devices?status=success&user_id=4c9a7e15-6d3b-42f8-91c0-8e5b2a7d04f6
status
successConnected. The tokens are stored and data will start arriving.
deniedThey declined at the provider. Nothing was linked and nothing was shared.
errorSomething went wrong on our side or the provider's. Nothing was saved.

Handle all three — denied is a normal outcome, not a failure.

Don't treat this redirect as the source of truth. It tells you where to send the browser next; the account.connected event is what tells your backend the connection exists. A user who closes the tab mid-redirect still connected.

4. Get the event

With stridee listen running, this arrives on your local handler the moment they finish:

account.connected
{
  "id": "9f2c1e7a-4b83-4d21-9a6e-3c5f0d8b71a4",
  "type": "account.connected",
  "created": "2026-08-05T11:16:44Z",
  "webhook_id": "2d7b45c1-8e0a-4f36-b512-9c7d3a6e04f8",
  "nonce": "Kd4nWpLbEa9xTvRm2Cj7Lz0Bq2vNhCz7",
  "user_id": "4c9a7e15-6d3b-42f8-91c0-8e5b2a7d04f6",
  "provider": "coros",
  "data": {
    "connection_id": "a2f81b60-3c47-49d5-b8e2-70a4c9f13d85"
  }
}

user_id is the value /v1/connect returned, which is how you map it back to your own user. From then on their workouts arrive as activity.created — see Events for the shape and Getting the file for the recording.

That is the whole path. Users & connections covers what comes after: listing, disconnecting and forgetting users.

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