> ## Documentation Index
> Fetch the complete documentation index at: https://docs.syncline.run/llms.txt
> Use this file to discover all available pages before exploring further.

# User Calendar OAuth

> Connect users' Google Calendars to your platform

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

```javascript theme={null}
// 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.

```bash theme={null}
GET https://api.syncline.run/v1/platform/oauth-url?user_email=alice@example.com
Authorization: Bearer sk_live_xyz789...
```

Response:

```json theme={null}
{
  "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!)        |

<Warning>
  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.
</Warning>

## OAuth URL Parameters

<ParamField query="platform_key" type="string" required>
  Your publishable key (`pk_live_xxx`)
</ParamField>

<ParamField query="user_email" type="string" required>
  The email address of the user who needs to connect their calendar
</ParamField>

<ParamField query="redirect_uri" type="string">
  Where to redirect after successful OAuth. Must be in your allowed redirect URIs.
</ParamField>

<ParamField query="state" type="string">
  Optional state parameter for CSRF protection. Will be returned to your redirect URI.
</ParamField>

## User Flow

```mermaid theme={null}
sequenceDiagram
    participant Agent as Your AI Agent
    participant User
    participant Syncline
    participant Google

    Agent->>User: "Click here to connect your calendar: [oauth_url]"
    User->>Syncline: Clicks link
    Syncline->>Google: Redirect to Google consent
    User->>Google: Grants calendar access
    Google->>Syncline: Authorization code
    Syncline->>Syncline: Store calendar connection
    Syncline->>User: Redirect to your redirect_uri?success=true
```

## Complete Example

### Step 1: Send OAuth Link to User

```javascript theme={null}
// 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

```javascript theme={null}
// 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

```javascript theme={null}
// 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

```javascript theme={null}
// 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

```javascript theme={null}
// 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

```javascript theme={null}
// 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:

```javascript theme={null}
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:

```javascript theme={null}
// 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

```json theme={null}
{
  "error": "Invalid email format",
  "message": "Please provide a valid email address"
}
```

### Platform Not Found

```json theme={null}
{
  "error": "Unauthorized",
  "message": "Invalid or missing API key"
}
```

## Related Endpoints

<CardGroup cols={3}>
  <Card title="Check User Status" icon="circle-check" href="/api-reference/platform-user-status">
    Verify if user has connected
  </Card>

  <Card title="List Connected Users" icon="users" href="/api-reference/platform-users">
    See all connected users
  </Card>

  <Card title="Platform Settings" icon="gear" href="/api-reference/platform-settings">
    Configure redirect URIs
  </Card>
</CardGroup>
