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
The email address of the user who needs to connect their calendar
Optional redirect URI after successful OAuth. If not provided, uses your platform’s default redirect URI configured in settings.
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
The URL to redirect your user to for OAuth authorization
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
{
"error": "Invalid email format",
"message": "Please provide a valid email address"
}
{
"error": "Unauthorized",
"message": "Invalid or missing API key"
}