Connecting via OAuth2

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.

When to use OAuth

Choose OAuth2 when:

Use an API key instead if:

Register an OAuth app

  1. Log in to your yAppointment admin panel
  2. Navigate to Integrations > OAuth Apps
  3. Click Create New App
  4. Fill in the details:
  5. Click Save
  6. You'll receive your Client ID and Client Secret — store the secret securely

Authorization request with PKCE

PKCE (Proof Key for Code Exchange) prevents authorization code interception attacks. Generate a code challenge before redirecting the user to the authorization endpoint.

Generate PKCE code verifier and challenge

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

Redirect to authorization endpoint

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

User consent & redirect

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)

Token exchange with code verifier

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

Using access tokens

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

Refresh token rotation

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

Revocation

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.