CorrConnectDocs
Early accessRequest access

API reference

OAuth endpoints

Start the athlete's authorization, swap codes and refresh tokens, and give access back. The concepts are in OAuth 2.0 with PKCE.

Ask an athlete for access

GET/oauth/authorize

Browser page, not an API call. Send the athlete here; CorrConnect checks every value before it shows sign-in. After the athlete decides, the browser goes to redirect_uri with code and state (approved) or error=access_denied and state (denied). A request that fails the checks gets a 400 error page and is not redirected.

Auth
None
Host
Portal origin only

Parameters

NameInTypeNotes
response_type requiredquerystringAlways code. One of code
client_id requiredquerystringYour app's client_id.
redirect_uri requiredquerystringOne of the app's registered callbacks, character for character.
scope requiredquerystringSpace-separated scopes, for example activities:read profile:read.
state requiredquerystringRandom value of at least 8 characters; it comes back unchanged. at least 8 characters
code_challenge requiredquerystringBASE64URL(SHA256(code_verifier)) without padding (43 to 128 characters). 43 to 128 characters
code_challenge_method requiredquerystringAlways S256. One of S256

Response 200

The HTML sign-in and consent page. After the athlete decides, the page sends the browser to redirect_uri with code and state, or error=access_denied and state.

Errors

StatusCodeWhen
400unsupported_authorization_requestresponse_type is not code or code_challenge_method is not S256.
400invalid_authorization_requeststate is shorter than 8 characters or code_challenge is malformed.
400invalid_clientNo app has this client_id.
400redirect_uri_mismatchredirect_uri is not one of the app's registered callbacks, exactly.
400invalid_scopeA requested scope is unknown or the scope list is empty.
Browser URL (one line)
CORRCONNECT_BASE/oauth/authorize
  ?response_type=code
  &client_id=app_0123456789abcdef0123456789abcdef
  &redirect_uri=https%3A%2F%2Fdeveloper.example%2Fcallback
  &scope=activities%3Aread%20profile%3Aread
  &state=<random, 8+ characters>
  &code_challenge=<BASE64URL(SHA256(code_verifier))>
  &code_challenge_method=S256

Exchange a code or refresh a token

POST/oauth/token

Public-client token endpoint; no client secret. grant_type=authorization_code exchanges a one-use code with its PKCE verifier. grant_type=refresh_token sends client_id and refresh_token (and optionally a narrower scope); each refresh token works once and the response carries its replacement. Presenting a used refresh token again revokes the whole grant.

Auth
None
Host
Portal origin or API host

Request body application/x-www-form-urlencoded

FieldTypeNotes
grant_type requiredstringauthorization_code or refresh_token.
client_id requiredstringYour app's client_id.
codestring or nullauthorization_code only: the one-use code from your callback.
redirect_uristring or nullauthorization_code only: the same redirect_uri you sent to /oauth/authorize.
code_verifierstring or nullauthorization_code only: the PKCE verifier behind the code_challenge.
refresh_tokenstring or nullrefresh_token only: your current refresh token.
scopestring or nullrefresh_token only, optional: ask for fewer scopes than the grant holds.

Response 200

One-hour default bearer token; actual lifetime is expires_in seconds.

FieldTypeNotes
access_token requiredstring
token_type requiredstringOne of Bearer
expires_in requiredinteger
scope requiredstring
refresh_tokenstring
refresh_token_expires_ininteger

Errors

StatusCodeWhen
400invalid_requestA required field is missing or malformed.
400invalid_clientclient_id is not a CorrConnect app ID.
400invalid_grantThe code or refresh token is invalid, expired, used or revoked, the verifier or redirect_uri does not match, or a refresh asked for scopes the grant lacks.
400unsupported_grant_typegrant_type is not authorization_code or refresh_token.
429slow_downToo many failed attempts, or too many token requests from one address.
cURL
curl "$CORRCONNECT_BASE/oauth/token" \
  -d grant_type=authorization_code \
  --data-urlencode "client_id=$CLIENT_ID" \
  --data-urlencode "code=$CODE" \
  --data-urlencode "redirect_uri=$REDIRECT_URI" \
  --data-urlencode "code_verifier=$CODE_VERIFIER"
JavaScript
const response = await fetch(`${process.env.CORRCONNECT_BASE}/oauth/token`, {
  method: "POST",
  body: new URLSearchParams({
    grant_type: "authorization_code",
    client_id: process.env.CLIENT_ID,
    code: process.env.CODE,
    redirect_uri: process.env.REDIRECT_URI,
    code_verifier: process.env.CODE_VERIFIER,
  }),
});
console.log(response.status, await response.json());
Python
import os

import requests

response = requests.post(
    f"{os.environ['CORRCONNECT_BASE']}/oauth/token",
    data={
        "grant_type": "authorization_code",
        "client_id": os.environ["CLIENT_ID"],
        "code": os.environ["CODE"],
        "redirect_uri": os.environ["REDIRECT_URI"],
        "code_verifier": os.environ["CODE_VERIFIER"],
    },
    timeout=10,
)
print(response.status_code, response.json())
Swift
import Foundation

let env = ProcessInfo.processInfo.environment
var request = URLRequest(url: URL(string: "\(env["CORRCONNECT_BASE"]!)/oauth/token")!)
request.httpMethod = "POST"
request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
var form = URLComponents()
form.queryItems = [
    URLQueryItem(name: "grant_type", value: "authorization_code"),
    URLQueryItem(name: "client_id", value: env["CLIENT_ID"]!),
    URLQueryItem(name: "code", value: env["CODE"]!),
    URLQueryItem(name: "redirect_uri", value: env["REDIRECT_URI"]!),
    URLQueryItem(name: "code_verifier", value: env["CODE_VERIFIER"]!),
]
request.httpBody = form.percentEncodedQuery!.replacingOccurrences(of: "+", with: "%2B").data(using: .utf8)
let (data, response) = try await URLSession.shared.data(for: request)
print((response as! HTTPURLResponse).statusCode, String(decoding: data, as: UTF8.self))
Response 200
{
  "access_token": "<opaque-access-token>",
  "token_type": "Bearer",
  "expires_in": 3600,
  "scope": "activities:read profile:read",
  "refresh_token": "<opaque-refresh-token>",
  "refresh_token_expires_in": 5184000
}

Revoke one token pair

POST/oauth/revoke-token

Releases one token and the token issued with it; the grant stays active. Send the access token as a Bearer header, or a form body with token (access or refresh), optionally token_type_hint and client_id, as in RFC 7009.

Auth
Bearer access token
Host
Portal origin or API host

Request body application/x-www-form-urlencoded

FieldTypeNotes
tokenstring or nullAn access or refresh token, if you are not sending a Bearer header.
token_type_hintstring or nullOptional: access_token or refresh_token. CorrConnect checks both either way.
client_idstring or nullOptional: when sent, a token from another app is ignored.

Response 200

Idempotent release; token existence is not disclosed.

FieldTypeNotes
status requiredstringOne of revoked

Errors

StatusCodeWhen
401invalid_access_tokenNeither a Bearer header nor a token field was sent.
422validation_failedA field is missing, has the wrong type or is out of range; details.fields says which.
cURL
curl -X POST "$CORRCONNECT_BASE/oauth/revoke-token" \
  -H "Authorization: Bearer $ACCESS_TOKEN"
JavaScript
const response = await fetch(`${process.env.CORRCONNECT_BASE}/oauth/revoke-token`, {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${process.env.ACCESS_TOKEN}`,
  },
});
console.log(response.status, await response.json());
Python
import os

import requests

response = requests.post(
    f"{os.environ['CORRCONNECT_BASE']}/oauth/revoke-token",
    headers={"Authorization": f"Bearer {os.environ['ACCESS_TOKEN']}"},
    timeout=10,
)
print(response.status_code, response.json())
Swift
import Foundation

let env = ProcessInfo.processInfo.environment
var request = URLRequest(url: URL(string: "\(env["CORRCONNECT_BASE"]!)/oauth/revoke-token")!)
request.httpMethod = "POST"
request.setValue("Bearer \(env["ACCESS_TOKEN"]!)", forHTTPHeaderField: "Authorization")
let (data, response) = try await URLSession.shared.data(for: request)
print((response as! HTTPURLResponse).statusCode, String(decoding: data, as: UTF8.self))
Response 200
{
  "status": "revoked"
}

End the whole grant

POST/oauth/revoke

Send the access token as a Bearer header. The athlete's grant for your app ends with every code and token in it, and subscribed apps get grant.revoked. Expired tokens still work here; unknown tokens get the same answer.

Auth
Bearer access token
Host
Portal origin or API host

Response 200

Idempotent release; token existence is not disclosed.

FieldTypeNotes
status requiredstringOne of revoked

Errors

StatusCodeWhen
401invalid_access_tokenNo Bearer header was sent.
cURL
curl -X POST "$CORRCONNECT_BASE/oauth/revoke" \
  -H "Authorization: Bearer $ACCESS_TOKEN"
JavaScript
const response = await fetch(`${process.env.CORRCONNECT_BASE}/oauth/revoke`, {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${process.env.ACCESS_TOKEN}`,
  },
});
console.log(response.status, await response.json());
Python
import os

import requests

response = requests.post(
    f"{os.environ['CORRCONNECT_BASE']}/oauth/revoke",
    headers={"Authorization": f"Bearer {os.environ['ACCESS_TOKEN']}"},
    timeout=10,
)
print(response.status_code, response.json())
Swift
import Foundation

let env = ProcessInfo.processInfo.environment
var request = URLRequest(url: URL(string: "\(env["CORRCONNECT_BASE"]!)/oauth/revoke")!)
request.httpMethod = "POST"
request.setValue("Bearer \(env["ACCESS_TOKEN"]!)", forHTTPHeaderField: "Authorization")
let (data, response) = try await URLSession.shared.data(for: request)
print((response as! HTTPURLResponse).statusCode, String(decoding: data, as: UTF8.self))
Response 200
{
  "status": "revoked"
}