The OAuth2 Authorization Code flow with PKCE is the recommended way for third-party integrations to access yAppointment data on behalf of a company admin. Use this flow when your application serves multiple yAppointment customers who need to grant consent rather than sharing a secret key directly.
Choose OAuth2 when:
Use an API key instead if:
https://myapp.com/callback)bookings:read — view customer bookingsservices:read — view services and pricingcalendars:read — view availability and time slotscustomers:read — view customer listPKCE (Proof Key for Code Exchange) prevents authorization code interception attacks. Generate a code challenge before redirecting the user to the authorization endpoint.
// Node.js / TypeScript example
import crypto from 'crypto'
function generatePKCEChallenge() {
// Step 1: Create a random 43-128 character string (code verifier)
const codeVerifier = crypto.randomBytes(32).toString('base64url')
// Step 2: Create SHA256 hash of the code verifier (code challenge)
const codeChallenge = crypto
.createHash('sha256')
.update(codeVerifier)
.digest('base64url')
return { codeVerifier, codeChallenge }
}
const { codeVerifier, codeChallenge } = generatePKCEChallenge()
console.log('Code Verifier:', codeVerifier)
console.log('Code Challenge:', codeChallenge)
Store the codeVerifier in the user session (encrypted), then redirect to yAppointment:
const clientId = 'your_client_id_here'
const redirectUri = 'https://myapp.com/callback'
const state = crypto.randomBytes(16).toString('hex') // CSRF protection
const authUrl = new URL('https://yappointment.com/oauth/authorize')
authUrl.searchParams.set('client_id', clientId)
authUrl.searchParams.set('redirect_uri', redirectUri)
authUrl.searchParams.set('response_type', 'code')
authUrl.searchParams.set('scope', 'bookings:read services:read')
authUrl.searchParams.set('state', state)
authUrl.searchParams.set('code_challenge', codeChallenge)
authUrl.searchParams.set('code_challenge_method', 'S256')
// Redirect user to authUrl
res.redirect(authUrl.toString())
The user logs into yAppointment and sees a consent screen showing:
If they authorize, yAppointment redirects back to your redirectUri with:
https://myapp.com/callback?code=auth_code_here&state=your_state_value
Always validate the state parameter to prevent CSRF attacks:
const { code, state: returnedState } = req.query
if (returnedState !== sessionState) {
return res.status(400).json({ error: 'Invalid state parameter' })
}
// Code is valid, exchange it for a token (next step)
Exchange the authorization code for an access token. This is a backend-to-backend call
that includes your Client Secret and the code_verifier.
import axios from 'axios'
async function exchangeAuthCodeForToken(
code: string,
codeVerifier: string,
clientId: string,
clientSecret: string
) {
const response = await axios.post(
'https://api.yappointment.com/oauth/token',
{
grant_type: 'authorization_code',
code,
client_id: clientId,
client_secret: clientSecret,
redirect_uri: 'https://myapp.com/callback',
code_verifier: codeVerifier
}
)
return response.data
// Returns: {
// "access_token": "yapp_at_...",
// "token_type": "Bearer",
// "expires_in": 3600,
// "refresh_token": "yapp_rt_..."
// }
}
// In your callback handler:
const tokens = await exchangeAuthCodeForToken(
code,
sessionCodeVerifier,
clientId,
clientSecret
)
// Store tokens in database, associated with the user's company
Include the access token in the Authorization header for all API requests:
import axios from 'axios'
const api = axios.create({
baseURL: 'https://api.yappointment.com',
headers: {
Authorization: `Bearer ${accessToken}`
}
})
// Fetch the company info
const companyResponse = await api.post('/api/v1/me')
console.log('Company:', companyResponse.data)
// List all bookings
const bookingsResponse = await api.post('/api/v1/bookings', {
filter: { status: 'confirmed' }
})
console.log('Bookings:', bookingsResponse.data)
Access tokens expire after 1 hour. When you get a 401 error, refresh the token using the refresh token (see next section).
When your access token expires, use the refresh token to get a new one:
async function refreshAccessToken(
refreshToken: string,
clientId: string,
clientSecret: string
) {
const response = await axios.post(
'https://api.yappointment.com/oauth/token',
{
grant_type: 'refresh_token',
refresh_token: refreshToken,
client_id: clientId,
client_secret: clientSecret
}
)
return response.data
// Returns a new access token and refresh token
}
// Use this to refresh when you get a 401:
if (error.response?.status === 401) {
const newTokens = await refreshAccessToken(
storedRefreshToken,
clientId,
clientSecret
)
// Update your stored tokens
await db.updateTokens(companyId, newTokens)
// Retry the original request
api.defaults.headers.Authorization = `Bearer ${newTokens.access_token}`
return api.request(originalRequest)
}
When a customer wants to disconnect your app, revoke the access token:
async function revokeToken(
accessToken: string,
clientId: string,
clientSecret: string
) {
await axios.post(
'https://api.yappointment.com/oauth/revoke',
{
token: accessToken,
client_id: clientId,
client_secret: clientSecret
}
)
}
// After revocation, the token no longer works
Once revoked, the customer will need to re-authorize your app to restore access. You can also provide a Disconnect button in your app's settings to let users revoke without visiting your admin panel.