Developer Documentation
CP OAuth implements the OAuth 2.0 Authorization Code flow with PKCE support. Third-party applications can authenticate users and request granular access to their competitive programming profile data.
The standard OAuth2 Authorization Code flow works as follows:
- Your application redirects the user to our /oauth/authorize endpoint with your client_id, redirect_uri, and requested scopes.
- The user sees a consent screen and decides whether to grant access.
- If approved, the user is redirected to your redirect_uri with an authorization code.
- Your server exchanges the authorization code for an access token and refresh token via POST /oauth/token.
- Use the access token to call GET /oauth/userinfo and retrieve the user's data.
- When the access token expires, use the refresh token to obtain a new access token via POST /oauth/token with grant_type=refresh_token.
GET /api/oauth/authorize
Initiates the authorization flow. Redirects the user to the consent screen.
GET /oauth/authorize?
response_type=code
&client_id=YOUR_CLIENT_ID
&redirect_uri=https://yourapp.com/callback
&scope=openid profile
&state=random_state_string
&code_challenge=BASE64URL_SHA256_HASH
&code_challenge_method=S256POST /api/oauth/token
Exchanges an authorization code for an access token and refresh token. Also supports refreshing tokens via grant_type=refresh_token.
// Exchange authorization code for access token + refresh token
const response = await fetch('/api/oauth/token', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
grant_type: 'authorization_code',
code: 'AUTHORIZATION_CODE',
redirect_uri: 'https://yourapp.com/callback',
client_id: 'YOUR_CLIENT_ID',
client_secret: 'YOUR_CLIENT_SECRET',
// Or for PKCE:
// code_verifier: 'YOUR_CODE_VERIFIER'
})
})
const {
access_token, // JWT, expires in 1 hour
refresh_token, // opaque, expires in 30 days
token_type,
expires_in,
scope
} = await response.json()POST /api/oauth/token (refresh)
Exchanges an authorization code for an access token and refresh token. Also supports refreshing tokens via grant_type=refresh_token.
// Refresh an expired access token
const response = await fetch('/api/oauth/token', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
grant_type: 'refresh_token',
refresh_token: 'YOUR_REFRESH_TOKEN',
client_id: 'YOUR_CLIENT_ID',
client_secret: 'YOUR_CLIENT_SECRET' // optional for PKCE clients
})
})
// Returns new access_token + new refresh_token (rotation)
const { access_token, refresh_token, expires_in } = await response.json()GET /api/oauth/userinfo
Returns user data filtered by the scopes granted during authorization.
// Fetch user data with access token
const userinfo = await fetch('/api/oauth/userinfo', {
headers: { Authorization: 'Bearer {access_token}' }
})
const data = await userinfo.json()
// Response varies based on granted scopes:
// {
// sub,
// username,
// display_name,
// avatar_url,
// bio,
// email,
// email_verified,
// linked_accounts,
// cp_summary,
// cp_details
// }POST /api/oauth/revoke
Revokes an access token or refresh token (RFC 7009). Revoking a refresh token also invalidates all associated access tokens.
// Revoke a token (RFC 7009)
await fetch('/api/oauth/revoke', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
token: 'TOKEN_TO_REVOKE',
token_type_hint: 'refresh_token' // or 'access_token'
})
})
// Always returns 200, even if the token was already invalidRequest only the scopes your application needs. The /oauth/userinfo response is dynamically filtered based on the granted scopes.
For public clients (e.g. SPAs, mobile apps) that cannot securely store a client_secret, use PKCE instead. Generate a code_verifier, derive a code_challenge using SHA-256, and include it in the authorization request.
// Generate PKCE code verifier and challenge
const codeVerifier = generateRandomString(128)
const encoder = new TextEncoder()
const data = encoder.encode(codeVerifier)
const digest = await crypto.subtle.digest('SHA-256', data)
const codeChallenge = btoa(String.fromCharCode(...new Uint8Array(digest)))
.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '')
// Include in authorization request:
// code_challenge=codeChallenge
// code_challenge_method=S256
// Include in token request:
// code_verifier=codeVerifier (instead of client_secret)Generate an SVG card image showing a user's linked platform accounts. Embed it in GitHub READMEs, blogs, or anywhere that supports images. Only publicly visible platforms are shown.
Endpoint:GET /api/users/{username}/card.svg
Parameters: width (default 480, range 300-800), theme (light or dark, default light), lang (en/zh/ja, default en)
Example (Markdown):

<!-- Dark theme -->

<!-- Custom width -->
