OAuth2
OAuth2 lets athletes connect their existing Saturday accounts to your platform. Instead of creating new partner-scoped athletes, you request access to the athlete’s Saturday profile, with their explicit consent.When to use OAuth2
Most partners start with API keys. Add OAuth2 when athletes tell you they have Saturday accounts.
The flow
1
Redirect to authorize
Your app redirects the athlete to Saturday’s authorization page
2
Athlete authenticates
Athlete signs in to their Saturday account with Google, Apple, or email
3
Athlete consents
Athlete sees the scopes you are requesting and clicks “Allow”
4
Code redirect
Saturday redirects back to your app with an authorization code
5
Token exchange
Your server exchanges the code for access and refresh tokens
6
API calls
Use the access token as a Bearer token for API requests
/v1/oauth/login, /v1/oauth/session, and /v1/oauth/consent. The authorize endpoint routes the athlete through them; your app never calls them directly.
Step 1: Generate PKCE challenge
Saturday requires PKCE (Proof Key for Code Exchange) for all OAuth2 flows. Generate a code verifier and challenge:Step 2: Redirect to authorize
Build the authorization URL and redirect the athlete:Step 3: Handle the callback
After the athlete consents, Saturday redirects to yourredirect_uri with a code:
state parameter matches what you sent before you exchange the code. Saturday echoes state back but does not validate it for you, so this check is yours to make; it is what prevents a CSRF attack from injecting an attacker’s authorization code into your user’s session.
If the athlete denies access:
Step 4: Exchange code for tokens
access_token, token_type (Bearer), expires_in (3600), refresh_token (opaque, prefixed srt_), and scope, the granted scopes joined by spaces. client_secret is required only for confidential pre-registered clients; public clients, including every metadata-document client, authenticate with PKCE alone. redirect_uri is optional on the exchange, and when sent it must match the one used at authorize.
Step 5: Use the access token
The access token is a JWT that contains the partner ID, athlete UID, and granted scopes. Use it as a Bearer token:athlete_id; it is in the JWT, alongside the partner ID and the granted scopes.
Step 6: Refresh expired tokens
Access tokens expire after 1 hour. Use the refresh token to get a new pair:client_id is optional on a refresh; when sent, it must match the client the token was issued to.
Available scopes
offline_access grants no data access by itself. It is the OIDC and MCP signal that you want to stay connected without sending the athlete back through consent every hour. Request it alongside your data scopes if you need refresh. It is not included in the default athlete:read fallback.
ai:chat is available to pre-registered clients only. It is deliberately excluded from the scopes advertised in discovery metadata, so a client registering through a Client ID Metadata Document cannot request it.
A pre-registered client is granted a fixed scope allowlist at registration. Requesting a scope outside it fails the authorize request with invalid_scope; contact api-support@saturday.fit to widen it.
Coach scopes
A coach on Pro Coach tier or higher (Pro Coach, Head Coach, Business, or Enterprise) can additionally request coach scopes. These unlock the Coach API and the coach tools in the Claude connector. A coach’s OAuth token also carries the athlete-self facet: the same token reads the coach’s athlete data on/v1/athletes/* and their roster on /v1/coach/*, and the route decides which applies.
Tier is re-checked on every request. If a coach’s tier lapses, the coach scopes stop resolving and the token quietly degrades to athlete-self access rather than erroring, so handle a suddenly empty roster as a billing state, not an outage.
When coach scopes are requested, the consent screen discloses that the app will be able to read the coach’s athletes’ fueling data and manage the coach’s alert settings.
Request only the scopes you need. Athletes are more likely to consent when the request is minimal.
Client registration
Saturday supports two registration mechanisms:Client ID Metadata Documents
CIMD is the MCP 2025-11-25 spec’s preferred registration mechanism (draft-ietf-oauth-client-id-metadata-document). Instead of registering, host a JSON document describing your client at a stable HTTPS URL on a domain you control, and pass that URL as yourclient_id:
client_id. Requirements:
- The URL must be
https, use a hostname rather than an IP literal, contain a path, and carry no fragment, userinfo, or dot segments. - The document’s
client_idfield must exactly match the document URL. redirect_urismust behttpsURLs or loopback (http://localhost/http://127.0.0.1) URIs. Loopback URIs match on any port (RFC 8252).- CIMD clients are public clients:
token_endpoint_auth_methodmust benone(or absent), and PKCE is mandatory. Confidential CIMD clients (private_key_jwt) are not supported. grant_types, when present, must includeauthorization_code;response_types, when present, must includecode.- The fetch expects a
200with a JSON content type within 5 seconds and follows no redirects, so a host that redirects to awww.variant fails resolution. Documents over 16 KB are rejected. - Documents are cached per their
Cache-Controlheaders (clamped between 5 minutes and 24 hours, one hour when the header is absent), so metadata changes propagate within that window. Failed fetches and invalid documents are never cached.
"client_id_metadata_document_supported": true in its Authorization Server Metadata, so spec-compliant MCP clients, including Claude, select CIMD automatically.
Discovery (remote MCP connectors)
For the Claude connector, clients auto-discover the authorization server, with no manual configuration. Saturday serves the standard metadata documents, derived per-environment from the request host:
The Authorization Server Metadata names the authorize, token, and revocation endpoints,
code_challenge_methods_supported of S256 only, token_endpoint_auth_methods_supported of none, and scopes_supported: the five data scopes, the four coach scopes, and offline_access, with ai:chat omitted.
An unauthenticated request to /mcp returns 401 with a WWW-Authenticate: Bearer resource_metadata="…" header pointing at the discovery document, which bootstraps the OAuth handshake. The connector is a public client (PKCE, no client secret) and may use loopback redirects (http://127.0.0.1:<any-port>, RFC 8252) for local CLIs such as Claude Code.
Resource indicators (RFC 8707)
Saturday implements RFC 8707 Resource Indicators, the token audience binding required by the MCP authorization spec. A client names the resource it intends to use the token with via theresource parameter on the authorize and token requests, using the canonical MCP server URI:
resource is supplied, Saturday binds it into the access token’s aud claim, and the MCP endpoint rejects any token whose audience was issued for a different resource. A token minted for Saturday can only be used against Saturday; it cannot be replayed against another server. Tokens minted before audience binding shipped, and partner keys that never traverse this flow, carry no audience and are accepted as before.
A
resource that is malformed (not an absolute URI, or carries a fragment) or names a resource this server does not serve is rejected with invalid_target. Comparison is canonical: scheme and host are case-insensitive and a trailing slash is ignored. On token exchange and on refresh, a resource may repeat the originally-granted resource but cannot retarget the token; the minted audience is always the one consented to at authorize.
Error scenarios
Errors follow the RFC 6749 shape,{"error": "...", "error_description": "..."}. Branch on error; the description is for your logs and may change.
At the authorize endpoint an unknown
client_id is invalid_request; at the token endpoint it is invalid_client.
Access token failures are different in shape. A request carrying an expired token, a token for another resource, or a malformed token all return 401 in the standard API error envelope with type authentication_error, code invalid_api_key, and the single message Invalid or expired access token. The cause is deliberately not distinguished in the response, so treat any 401 on an API call as “refresh, then retry once, then re-authorize” rather than trying to parse which failure it was.
The subscription-lapse case deserves distinct handling: its description tells the athlete to resubscribe, and the refresh token is deliberately not consumed, so the same token works again once they do.
Revoking tokens
Athletes can revoke access from their Saturday account settings. Partners can revoke programmatically:200 whether or not the token existed, so a repeated revoke is not an error.