The authorization code flow
Authorization code with PKCE, which is the flow for any application acting on behalf of a person. Every example below is a real request.
1. Make a PKCE verifier and challenge
PKCE is required, for confidential clients too, and only S256 is accepted — plain is
refused.
VERIFIER=$(openssl rand -base64 60 | tr -d '\n=+/' | cut -c1-64)
CHALLENGE=$(printf '%s' "$VERIFIER" | openssl dgst -sha256 -binary | openssl base64 | tr '+/' '-_' | tr -d '=')
Keep the verifier; you need it in step 4.
2. Send the member to Sporty
https://api.sporty.plus/oauth2/authorize
?response_type=code
&client_id=YOUR_CLIENT_ID
&redirect_uri=https://yourapp.example/callback
&scope=openid%20profile%20email%20club.courts.read
&state=RANDOM_PER_REQUEST
&code_challenge=CHALLENGE
&code_challenge_method=S256
| Parameter | Notes |
|---|---|
state | Random, unguessable, per request. Check it on the way back — that is what makes the callback yours. |
nonce | Optional. If you send it, it comes back in the id_token. Use it if you want one. |
club_id | Optional. Only meaningful for a client granted access to several clubs, to say which one this is about. |
The member sees a consent screen on my.sporty.plus listing, in plain language, exactly what
you asked for. They can approve less than you asked for; grant requests are never widened.
3. They come back to your redirect URI
https://yourapp.example/callback?code=...&state=...
If they declined, or something was wrong:
https://yourapp.example/callback?error=access_denied&error_description=...&state=...
Compare state with what you sent before doing anything else.
4. Exchange the code
Within a few minutes, and once only. A code is single use.
curl -X POST https://api.sporty.plus/oauth2/token \
-H 'Accept: application/json' \
-d grant_type=authorization_code \
-d client_id=YOUR_CLIENT_ID \
-d client_secret=YOUR_SECRET \
-d redirect_uri=https://yourapp.example/callback \
-d code_verifier="$VERIFIER" \
-d code=THE_CODE
Omit client_secret for a public client.
{
"token_type": "Bearer",
"expires_in": 3600,
"access_token": "eyJ0eXAiOiJKV1Qi...",
"refresh_token": "def5020089ab...",
"id_token": "eyJ0eXAiOiJKV1Qi..."
}
id_token is present only when you asked for openid. See
Sign in with Sporty.
5. Use it
curl https://api.sporty.plus/api/partner/v1/clubs/me \
-H "Authorization: Bearer $ACCESS_TOKEN"
6. Refresh
Access tokens last an hour; refresh tokens thirty days (a day for a self-registered client).
curl -X POST https://api.sporty.plus/oauth2/token \
-H 'Accept: application/json' \
-d grant_type=refresh_token \
-d client_id=YOUR_CLIENT_ID \
-d client_secret=YOUR_SECRET \
-d refresh_token=THE_REFRESH_TOKEN
7. Revoke, when you are done
curl -X POST https://api.sporty.plus/oauth2/token/revoke \
-d client_id=YOUR_CLIENT_ID \
-d client_secret=YOUR_SECRET \
-d token=THE_ACCESS_TOKEN
This always answers 200, whatever you send — that is what
RFC 7009 requires, so the endpoint cannot be used to
find out whether a token is valid.
A member can also revoke you at any moment, from Authorised applications in their profile. When that happens every token you hold stops working immediately, and you should send them through the flow again rather than retrying.
Errors you should expect
| Error | What happened |
|---|---|
invalid_request | Usually a missing or plain code_challenge. PKCE with S256 is required. |
invalid_scope | A scope that does not exist, or one this kind of client may not ask for. |
invalid_grant | The code was already spent, expired, or the code_verifier does not match. |
access_denied | The member said no, or the application is suspended. |
insufficient_scope (403) | The token is valid but was not granted this scope. The WWW-Authenticate header names what is needed. |
An unregistered redirect_uri is never redirected to. You will see the error on Sporty
instead — which is the point.