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.

Authorization Code Flow

The standard OAuth2 Authorization Code flow works as follows:

  1. Your application redirects the user to our /oauth/authorize endpoint with your client_id, redirect_uri, and requested scopes.
  2. The user sees a consent screen and decides whether to grant access.
  3. If approved, the user is redirected to your redirect_uri with an authorization code.
  4. Your server exchanges the authorization code for an access token and refresh token via POST /oauth/token.
  5. Use the access token to call GET /oauth/userinfo and retrieve the user's data.
  6. When the access token expires, use the refresh token to obtain a new access token via POST /oauth/token with grant_type=refresh_token.
API Endpoints

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=S256

POST /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 invalid
Permission Scopes

Request only the scopes your application needs. The /oauth/userinfo response is dynamically filtered based on the granted scopes.

PKCE (Proof Key for Code Exchange)

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)
User Profile Card

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):

![CP OAuth Profile](https://www.cpoauth.com/api/users/YOUR_USERNAME/card.svg)

<!-- Dark theme -->
![CP OAuth Profile](https://www.cpoauth.com/api/users/YOUR_USERNAME/card.svg?theme=dark)

<!-- Custom width -->
![CP OAuth Profile](https://www.cpoauth.com/api/users/YOUR_USERNAME/card.svg?width=600&theme=dark)