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¶
- PKCE is optional, not required. The plain Authorization Code flow with a client secret is fully supported.
- Credentials are self-service. Nobody emails you a client ID or secret. You get them from your app's OAuth tab.
- 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.
- 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.