Skip to main content

Overview

Generates a personalized OAuth URL to redirect your users to for connecting their Google Calendar. This is the first step in the user onboarding flow.

Authentication

Requires Platform API Key in the X-API-Key header.

Request

GET https://api.syncline.run/v1/platform/oauth-url?user_email=alice@example.com

Query Parameters

user_email
string
required
The email address of the user who needs to connect their calendar
redirect_uri
string
Optional redirect URI after successful OAuth. If not provided, uses your platform’s default redirect URI configured in settings.
state
string
Optional state parameter for CSRF protection and tracking. Will be returned to your redirect URI.

Response

{
  "oauth_url": "https://syncline.run/oauth/authorize?platform_key=sk_live_abc...&user_email=alice@example.com&state=user_123",
  "instructions": "Redirect your user to this URL to connect their Google Calendar"
}

Response Fields

oauth_url
string
The URL to redirect your user to for OAuth authorization
instructions
string
Human-readable instructions for what to do with the URL

User Flow

Example: Complete Onboarding Flow

Step 1: Generate OAuth URL

// User signs up in your app
async function startCalendarConnection(userEmail) {
  const response = await fetch(
    `https://api.syncline.run/v1/platform/oauth-url?user_email=${userEmail}&state=${userId}`,
    {
      headers: {
        'X-API-Key': process.env.SYNCLINE_API_KEY
      }
    }
  );

  const { oauth_url } = await response.json();

  // Redirect user to OAuth flow
  window.location.href = oauth_url;
}

Step 2: Handle OAuth Callback

// Your redirect URI handler
app.get('/oauth/callback', (req, res) => {
  const { state, success, error } = req.query;

  if (success === 'true') {
    const userId = state; // Your user ID from state parameter

    // Update user in your database
    db.updateUser(userId, { calendar_connected: true });

    // Show success message
    res.redirect('/dashboard?message=Calendar connected successfully!');
  } else {
    // Handle error
    res.redirect('/dashboard?error=' + error);
  }
});

Step 3: Verify Connection

// Check if user is connected before scheduling
async function checkConnection(userEmail) {
  const response = await fetch(
    `https://api.syncline.run/v1/platform/users/status?email=${userEmail}`,
    {
      headers: {
        'X-API-Key': process.env.SYNCLINE_API_KEY
      }
    }
  );

  const { connected } = await response.json();
  return connected;
}

Use Cases

New User Onboarding

// During signup flow
app.post('/signup', async (req, res) => {
  const { email, name } = req.body;

  // Create user in your database
  const user = await db.createUser({ email, name });

  // Get OAuth URL
  const { oauth_url } = await getOAuthURL(email, user.id);

  // Redirect immediately to calendar connection
  res.json({
    message: 'Account created! Please connect your calendar.',
    oauth_url
  });
});

Check Connection Status

// Before allowing user to schedule
async function ensureConnected(userEmail) {
  const isConnected = await checkConnection(userEmail);

  if (!isConnected) {
    // Get OAuth URL and prompt user
    const { oauth_url } = await getOAuthURL(userEmail);

    return {
      connected: false,
      message: 'Please connect your calendar first',
      oauth_url
    };
  }

  return { connected: true };
}

Reconnection Flow

// Handle expired tokens
app.post('/api/meetings/schedule', async (req, res) => {
  try {
    const meeting = await scheduleMeeting(req.body);
    res.json(meeting);
  } catch (error) {
    if (error.code === 'USER_NOT_CONNECTED') {
      // Token expired or revoked - prompt reconnection
      const { oauth_url } = await getOAuthURL(req.user.email);

      res.status(401).json({
        error: 'Calendar disconnected',
        message: 'Please reconnect your calendar',
        oauth_url
      });
    }
  }
});

Security Considerations

CSRF Protection with State

Always use the state parameter to prevent CSRF attacks:
const state = crypto.randomBytes(32).toString('hex');

// Store state in session/database
req.session.oauthState = state;

// Include in OAuth URL
const { oauth_url } = await getOAuthURL(userEmail, state);

// Verify on callback
app.get('/oauth/callback', (req, res) => {
  const { state } = req.query;

  if (state !== req.session.oauthState) {
    return res.status(400).send('Invalid state parameter');
  }

  // Process callback...
});

User Email Validation

Always validate the email belongs to your authenticated user:
// Bad: Accepting any email from query param
const email = req.query.email; // ❌ Vulnerable!

// Good: Use authenticated user's email
const email = req.user.email; // ✓ Secure

Error Responses

Invalid Email Format

{
  "error": "Invalid email format",
  "message": "Please provide a valid email address"
}

Platform Not Found

{
  "error": "Unauthorized",
  "message": "Invalid or missing API key"
}