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
Option A: Build the URL Yourself (Recommended)
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
| Key | Format | Where to Use |
|---|
| Publishable Key | pk_live_xxx | In OAuth URLs, client-side code (safe to expose) |
| Secret Key | sk_live_xxx | Server-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
Your publishable key (pk_live_xxx)
The email address of the user who needs to connect their calendar
Where to redirect after successful OAuth. Must be in your allowed redirect URIs.
Optional state parameter for CSRF protection. Will be returned to your redirect URI.
User Flow
Complete Example
Step 1: Send OAuth Link to User
// 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
{
"error": "Invalid email format",
"message": "Please provide a valid email address"
}
{
"error": "Unauthorized",
"message": "Invalid or missing API key"
}