Skip to main content

Overview

To schedule meetings on behalf of your users, they must first connect their Google Calendar. There are two ways to generate the OAuth URL.

Two Ways to Get the OAuth URL

Construct the URL directly using your publishable key (pk_live_xxx). This is simpler and doesn’t require an API call.
// Your publishable key (safe to expose in URLs)
const PUBLISHABLE_KEY = 'pk_live_abc123...'

function getOAuthURL(userEmail, redirectUri, state) {
  const params = new URLSearchParams({
    platform_key: PUBLISHABLE_KEY,
    user_email: userEmail,
    redirect_uri: redirectUri,
    state: state
  })

  return `https://api.syncline.run/v1/auth/google/authorize?${params}`
}

// Send this URL to your user
const oauthUrl = getOAuthURL('alice@example.com', 'https://myapp.com/callback', 'user_123')

Option B: Call the API Endpoint

Call the /v1/platform/oauth-url endpoint with your secret key (sk_live_xxx) to get a pre-built URL.
GET https://api.syncline.run/v1/platform/oauth-url?user_email=alice@example.com
Authorization: Bearer sk_live_xyz789...
Response:
{
  "oauth_url": "https://api.syncline.run/v1/auth/google/authorize?platform_key=pk_live_abc...&user_email=alice@example.com",
  "instructions": "Redirect your user to this URL to connect their Google Calendar"
}

Key Types

KeyFormatWhere to Use
Publishable Keypk_live_xxxIn OAuth URLs, client-side code (safe to expose)
Secret Keysk_live_xxxServer-side API calls only (keep secret!)
Never expose your secret key (sk_live_xxx) in URLs or client-side code. Always use your publishable key (pk_live_xxx) for OAuth URLs.

OAuth URL Parameters

platform_key
string
required
Your publishable key (pk_live_xxx)
user_email
string
required
The email address of the user who needs to connect their calendar
redirect_uri
string
Where to redirect after successful OAuth. Must be in your allowed redirect URIs.
state
string
Optional state parameter for CSRF protection. Will be returned to your redirect URI.

User Flow

Complete Example

// Publishable key (safe for URLs) - from your dashboard
const PUBLISHABLE_KEY = 'pk_live_abc123...'
// Secret key (server-side only) - from your dashboard
const SECRET_KEY = 'sk_live_xyz789...'

// Build OAuth URL (no API call needed)
function sendCalendarInvite(userEmail) {
  const oauthUrl = `https://api.syncline.run/v1/auth/google/authorize?` +
    `platform_key=${PUBLISHABLE_KEY}` +
    `&user_email=${encodeURIComponent(userEmail)}` +
    `&redirect_uri=${encodeURIComponent('https://myapp.com/callback')}` +
    `&state=${encodeURIComponent(userEmail)}`

  // Send to user via email, chat, SMS, etc.
  sendMessage(userEmail, `Connect your calendar: ${oauthUrl}`)
}

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