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
/authorizedirectly (rather than redirecting a browser), it responds with401 { "error": "login_required" }when there's no session, or200 { "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_tokenfrom 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
- Scopes — choose the permissions to request.
- Making requests — call the API with your token.