Documentation

Authorization code flow

Sign in a Davi user with OAuth 2.0 authorization code + PKCE.

This is the flow for signing in a Davi user and acting on their behalf. It uses PKCE, so it works for both confidential and public clients.

1. Create a PKCE pair

Generate a random code_verifier and derive its S256 code_challenge (code_challenge = BASE64URL(SHA256(code_verifier))):

function base64url(bytes) {
  return btoa(String.fromCharCode(...new Uint8Array(bytes)))
    .replace(/\+/g, "-")
    .replace(/\//g, "_")
    .replace(/=+$/, "");
}

const codeVerifier = base64url(crypto.getRandomValues(new Uint8Array(32)));
const codeChallenge = base64url(
  await crypto.subtle.digest("SHA-256", new TextEncoder().encode(codeVerifier)),
);

Keep the code_verifier for step 3; send only the code_challenge in step 2.

2. Send the user to /authorize

Direct the user's browser to the authorization endpoint. Davi handles sign-in and consent, then redirects back to your redirect_uri.

GET https://api.davi.social/oauth2/authorize
  ?response_type=code
  &client_id=YOUR_CLIENT_ID
  &redirect_uri=https://yourapp.com/callback
  &scope=openid profile
  &state=RANDOM_STATE
  &code_challenge=CODE_CHALLENGE
  &code_challenge_method=S256

Always send an unguessable state and verify it on return to protect against CSRF.

On success Davi redirects to:

https://yourapp.com/callback?code=AUTHORIZATION_CODE&state=RANDOM_STATE

If the user declines, you'll receive ?error=access_denied&state=... instead.

Programmatic callers: if you call /authorize directly (rather than redirecting a browser), it responds with 401 { "error": "login_required" } when there's no session, or 200 { "consent_required": true, ... } with the client and requested-scope details when consent hasn't been granted yet.

3. Exchange the code for tokens

Exchange the code at the token endpoint. Confidential clients authenticate with their secret; public clients rely on the code_verifier.

curl -X POST "https://api.davi.social/oauth2/token" \
  -u "YOUR_CLIENT_ID:YOUR_CLIENT_SECRET" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=authorization_code" \
  -d "code=AUTHORIZATION_CODE" \
  -d "redirect_uri=https://yourapp.com/callback" \
  -d "code_verifier=CODE_VERIFIER"
{
  "access_token": "eyJhbGciOi...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "def502...",
  "scope": "openid profile",
  "id_token": "eyJhbGciOi..."
}

Authorization codes are single-use and expire after 10 minutes.

4. Refresh the access token

Access tokens last one hour. Use the refresh token to get a new one:

curl -X POST "https://api.davi.social/oauth2/token" \
  -u "YOUR_CLIENT_ID:YOUR_CLIENT_SECRET" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=refresh_token" \
  -d "refresh_token=REFRESH_TOKEN"

You may request a narrower scope on refresh, but never a wider one.

Refresh tokens rotate on every use, and replaying a spent one revokes the session. Persist the new refresh_token from each response before using it, and make sure only one refresh runs at a time. Two concurrent refreshes — or a retry that re-sends an already-used token — will log the user out. This is the single most common integration bug, usually reported as "intermittent random logouts".

Signing out

Revoke a token with POST /oauth2/revoke. Users can also revoke your app's access at any time from their Davi settings, which invalidates its tokens.

Next