Skip to content

OAuth2 quick reference

Everything SweatStack-specific about the OAuth2 flow, on one page. SweatStack is a standard OAuth2 and OpenID Connect provider. If you have a working integration with another platform, it will almost certainly work here unchanged. For the full walk-through, see OAuth2. For the drop-in button that signs users in and onboards new ones in the same step, see Sign in with SweatStack.

Four things that differ from other platforms

  1. PKCE is optional, not required. The plain Authorization Code flow with a client secret is fully supported.
  2. Credentials are self-service. Nobody emails you a client ID or secret. You get them from your app's OAuth tab.
  3. The client secret is not created automatically. You create it under Client secrets on the OAuth tab. With PKCE you need no secret at all.
  4. There is no approval queue. Filling in the four required fields makes your app public the moment you save.

Endpoints

Authorization https://app.sweatstack.no/oauth/authorize
Token https://app.sweatstack.no/api/v1/oauth/token
OIDC discovery https://app.sweatstack.no/.well-known/openid-configuration
JWKS https://app.sweatstack.no/.well-known/jwks.json

If your library supports OpenID Connect discovery, point it at the discovery URL. It then configures every endpoint, scope, and signing key itself.

A complete authorization URL (scopes are space-delimited, URL-encoded):

https://app.sweatstack.no/oauth/authorize?response_type=code&client_id={client_id}&redirect_uri={redirect_uri}&scope=openid%20profile%20email%20activities:read%20offline_access

Authorization request parameters

Everything GET /oauth/authorize accepts. The first table is standard OAuth2 and OpenID Connect, with the places where SweatStack's behavior differs from the specification called out. The second table is SweatStack's own. Both of its parameters are optional.

Parameter Standard Values Notes
response_type required code Send it, as every client library does. SweatStack does not validate it: code is the only flow.
client_id required your Application ID Also brands the sign-in and consent screens with your app's name.
redirect_uri required a registered redirect URI See Redirect URIs.
scope required space-delimited scopes See Scopes.
state recommended any string Returned to you unchanged on the callback. Use it for CSRF protection.
code_challenge, code_challenge_method PKCE S256 A flow started with a challenge must finish with its verifier.
nonce OIDC, optional any string Echoed in the id_token that comes with the code exchange.
prompt OIDC, optional none, consent, login, select_account Omit it. Without prompt, the consent screen is skipped when the user is signed in and has already granted your app these scopes, and shown otherwise; a signed-out user gets the sign-in page. consent shows the screen even then. none also skips; where SweatStack differs from the specification, when none cannot skip it shows the sign-in or consent screen instead of returning login_required or consent_required to your redirect URI. Consent is always shown for a loopback (localhost) redirect, whatever prompt says, and to you on your own app while anyone else would be refused the request (why). login and select_account are accepted and behave as consent, except for a user of your app signed in on your own keys: there they show the sign-in screen, so the athlete can choose another account (Bring your own keys).
login_hint OIDC, optional an email address Fills in the email field when SweatStack asks the user for an email. Nothing else.
Parameter Default Values Effect
email_auth_mode enabled enabled, existing_only How the sign-in screen presents email. existing_only reframes it as "Sign in with an existing email account", so a new user self-selects into a wearable sign-in instead. It changes the wording, not the rules: a user who already has an email account signs in as before. Recommended for apps that need wearable data from day one.
integration none garmin_connect, intervals_icu, wahoo Name one provider and SweatStack skips the sign-in chooser: the user goes straight to that provider, and a signed-in user who has not connected it is sent to connect it before consent. One value per request. A misspelled value returns 422. See Send users straight to one provider.

Anything else on the URL is ignored.

Credentials

  • Client ID = the Client ID on your app's OAuth tab, also called the Application ID. Public. Safe to ship.
  • Client secret = created by you under Client secrets on the OAuth tab. Needed only for the flow without PKCE. Treat it like a password. Never ship it to a browser or a device.

Create an app at Settings → API.

Scopes

Space-delimited (RFC 6749 §3.3); commas also accepted.

Scope Grants
activities:read / activities:write Activities, tests, traces and the training analysis built from them. Write does not grant read.
wellness:read / wellness:write The daily measures, except the menstrual cycle day.
reproductive_health:read / reproductive_health:write The menstrual cycle day. Standalone.
workouts:read / workouts:write Scheduled workouts; write sends them to devices.
sharing:read / sharing:write Who shares with the user and whom they share with; delegated tokens; managed accounts.
openid, profile, email Sign-in, the name claims, the email address.
offline_access Issues a refresh token. Required for PKCE clients to get one; client-secret clients always do.

What each scope reaches and what the user reads: Scopes.

Pushing data in? If your integration uploads activities into SweatStack, request activities:write and see Uploading files: FIT and CSV, a per-file 202 response, and source_processed / source_failed webhooks.

PKCE

Supported and recommended, not required. A flow started with a code_challenge must be completed with its code_verifier. There is no downgrade to a secret. A flow started without PKCE uses the client secret.

Redirect URIs

Three accepted types (RFC 8252): claimed https, loopback http (localhost or 127.0.0.1, any port), and private-use custom schemes (com.example.app://oauth/callback, myapp://callback, reverse-DNS not required). https and loopback match by sub-path. Custom schemes match exactly, so register the precise string your app presents. Public plain http:// and URIs with a #fragment are rejected. Details: accepted formats.

Tokens

Token Lifetime
Access token 15 minutes (JWT, RS256).
Refresh token Until the user revokes your app, or 365 days unused.

On a 401, refresh and retry instead of restarting the OAuth flow. Token errors follow RFC 6749 §5.2: a JSON body with an error code (invalid_grant, invalid_client, invalid_request, and so on).

Going live

Your app is private (only you can connect) until you fill in four fields: description, URL, image, and privacy statement. Anyone else who opens your authorization link, including you on a second account, sees a "still private" page that names their account and lists what's missing. Save the four fields on your app's Consent screen tab and the app is public immediately: no review, no queue. Before anyone else can connect to a category, it also needs a purpose. See Going live.