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

# Calendar Connect

> Let your users connect their Google or Microsoft calendar via OAuth

Enable your users to connect their calendars to meetergo for automatic availability detection and event syncing.

## Why Calendar Connection?

Connected calendars enable:

* **Conflict detection** - Automatically block times when users have other events
* **Event creation** - Create calendar events when bookings are made
* **Two-way sync** - Keep meetergo and external calendars in sync

## Supported Providers

| Provider            | Features                            |
| ------------------- | ----------------------------------- |
| **Google Calendar** | Full read/write, multiple calendars |
| **Microsoft 365**   | Outlook calendar, Teams integration |

## Quick Start

Connecting a user's calendar is a simple 3-step OAuth flow:

<Steps>
  <Step title="Get OAuth URL">
    Call the API to get the provider's authorization URL
  </Step>

  <Step title="Redirect User">
    Send the user to Google/Microsoft to authorize access
  </Step>

  <Step title="Handle Callback">
    meetergo handles the OAuth callback and redirects to your `redirectUrl` with the result
  </Step>
</Steps>

## Implementation

### Step 1: Get OAuth Authorization URL

<CodeGroup>
  ```javascript JavaScript theme={null}
  async function connectCalendar(userId, provider, redirectUri) {
    const response = await fetch(
      `https://api.meetergo.com/v4/calendar-connections/auth/${provider}/url?` +
      new URLSearchParams({
        redirectUrl: redirectUri,
        setAsCalendarSync: 'true'  // Optional: auto-set as primary calendar
      }),
      {
        headers: {
          'Authorization': `Bearer ${process.env.MEETERGO_API_KEY}`,
          'x-meetergo-api-user-id': userId
        }
      }
    );

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

  // Usage
  const authUrl = await connectCalendar(
    'user-uuid',
    'google',  // or 'outlook'
    'https://yourapp.com/calendar/callback'
  );

  // Redirect user to OAuth
  window.location.href = authUrl;
  ```

  ```python Python theme={null}
  import requests
  import os

  def connect_calendar(user_id: str, provider: str, redirect_uri: str) -> str:
      response = requests.get(
          f'https://api.meetergo.com/v4/calendar-connections/auth/{provider}/url',
          params={
              'redirectUrl': redirect_uri,
              'setAsCalendarSync': 'true'
          },
          headers={
              'Authorization': f'Bearer {os.environ["MEETERGO_API_KEY"]}',
              'x-meetergo-api-user-id': user_id
          }
      )

      data = response.json()
      return data['authUrl']

  # Usage
  auth_url = connect_calendar(
      'user-uuid',
      'google',
      'https://yourapp.com/calendar/callback'
  )

  # Redirect user to OAuth (in your web framework)
  return redirect(auth_url)
  ```

  ```bash cURL theme={null}
  curl -X GET "https://api.meetergo.com/v4/calendar-connections/auth/google/url?redirectUrl=https://yourapp.com/callback&setAsCalendarSync=true" \
    -H "Authorization: Bearer ak_live:uuid:secret" \
    -H "x-meetergo-api-user-id: user-uuid"
  ```

  ```php PHP theme={null}
  <?php
  function connectCalendar($userId, $provider, $redirectUri) {
      $ch = curl_init();

      $url = "https://api.meetergo.com/v4/calendar-connections/auth/{$provider}/url?" .
             http_build_query([
                 'redirectUrl' => $redirectUri,
                 'setAsCalendarSync' => 'true'
             ]);

      curl_setopt_array($ch, [
          CURLOPT_URL => $url,
          CURLOPT_RETURNTRANSFER => true,
          CURLOPT_HTTPHEADER => [
              'Authorization: Bearer ' . getenv('MEETERGO_API_KEY'),
              'x-meetergo-api-user-id: ' . $userId
          ]
      ]);

      $response = json_decode(curl_exec($ch), true);
      curl_close($ch);

      return $response['authUrl'];
  }

  // Usage
  $authUrl = connectCalendar('user-uuid', 'google', 'https://yourapp.com/callback');
  header('Location: ' . $authUrl);
  exit;
  ```

  ```go Go theme={null}
  package main

  import (
      "encoding/json"
      "net/http"
      "net/url"
      "os"
  )

  func connectCalendar(userID, provider, redirectURI string) (string, error) {
      baseURL := "https://api.meetergo.com/v4/calendar-connections/auth/" + provider + "/url"

      params := url.Values{}
      params.Add("redirectUrl", redirectURI)
      params.Add("setAsCalendarSync", "true")

      req, _ := http.NewRequest("GET", baseURL+"?"+params.Encode(), nil)
      req.Header.Set("Authorization", "Bearer "+os.Getenv("MEETERGO_API_KEY"))
      req.Header.Set("x-meetergo-api-user-id", userID)

      client := &http.Client{}
      resp, err := client.Do(req)
      if err != nil {
          return "", err
      }
      defer resp.Body.Close()

      var result struct {
          AuthURL string `json:"authUrl"`
      }
      json.NewDecoder(resp.Body).Decode(&result)

      return result.AuthURL, nil
  }
  ```
</CodeGroup>

### Step 2: Handle the Callback

After the user authorizes access, they'll be redirected to your `redirectUrl` with query parameters:

**Success:**

```
https://yourapp.com/callback?success=true&provider=google&connectionId=abc-123
```

**Error:**

```
https://yourapp.com/callback?error=true&provider=google&message=Connection%2520already%2520exists
```

<CodeGroup>
  ```javascript JavaScript theme={null}
  // On your callback page
  function handleCalendarCallback() {
    const params = new URLSearchParams(window.location.search);

    const success = params.get('success') === 'true';
    const error = params.get('error') === 'true';
    const provider = params.get('provider');
    const connectionId = params.get('connectionId');
    const message = params.get('message');

    if (success) {
      console.log(`Connected ${provider} calendar!`, connectionId);
      // Show success message, redirect to settings, etc.
    } else if (error) {
      console.error(`Failed to connect ${provider}:`, decodeURIComponent(message));
      // Show error message with retry option
    }
  }
  ```

  ```python Python theme={null}
  # Flask example
  from flask import request, redirect
  from urllib.parse import unquote

  @app.route('/calendar/callback')
  def calendar_callback():
      success = request.args.get('success') == 'true'
      error = request.args.get('error') == 'true'
      provider = request.args.get('provider')
      connection_id = request.args.get('connectionId')
      message = unquote(request.args.get('message', ''))

      if success:
          flash(f'Successfully connected {provider} calendar!')
          return redirect('/settings')
      else:
          flash(f'Failed to connect calendar: {message}', 'error')
          return redirect('/settings?retry=calendar')
  ```
</CodeGroup>

## API Reference

### Get OAuth URL

```
GET /v4/calendar-connections/auth/{provider}/url
```

| Parameter           | Type  | Required | Description                                        |
| ------------------- | ----- | -------- | -------------------------------------------------- |
| `provider`          | path  | Yes      | `google` or `outlook`                              |
| `redirectUrl`       | query | No       | URL to redirect after OAuth (defaults to meetergo) |
| `userId`            | query | No       | Connect for another user (admin only)              |
| `setAsCalendarSync` | query | No       | Auto-set as primary calendar (`true`/`false`)      |

**Headers:**

| Header                   | Required | Description                       |
| ------------------------ | -------- | --------------------------------- |
| `Authorization`          | Yes      | `Bearer <api-key>`                |
| `x-meetergo-api-user-id` | Yes      | User UUID to connect calendar for |

**Response:**

```json theme={null}
{
  "authUrl": "https://accounts.google.com/o/oauth2/v2/auth?client_id=...",
  "provider": "google"
}
```

### List Calendar Connections

```
GET /v4/calendar-connections/connections
```

Get all calendar connections for a user:

<CodeGroup>
  ```javascript JavaScript theme={null}
  async function getConnections(userId) {
    const response = await fetch(
      'https://api.meetergo.com/v4/calendar-connections/connections',
      {
        headers: {
          'Authorization': `Bearer ${process.env.MEETERGO_API_KEY}`,
          'x-meetergo-api-user-id': userId
        }
      }
    );
    return response.json();
  }
  ```

  ```bash cURL theme={null}
  curl -X GET "https://api.meetergo.com/v4/calendar-connections/connections" \
    -H "Authorization: Bearer ak_live:uuid:secret" \
    -H "x-meetergo-api-user-id: user-uuid"
  ```
</CodeGroup>

**Response:**

```json theme={null}
{
  "connections": [
    {
      "id": "conn-123",
      "provider": "google",
      "email": "user@gmail.com",
      "isPrimary": true,
      "isConnected": true,
      "primaryCalendar": "primary",
      "selectedCalendars": ["primary"],
      "validateAvailability": true,
      "cancelIfDeclinedOrDeleted": true,
      "disableAttendeeNotification": false
    }
  ],
  "total": 1
}
```

### Get Connection Calendars

```
GET /v4/calendar-connections/connections/{connectionId}/calendars
```

Retrieve available calendars for a connection. Use this to let users choose which calendar to write events to and which to check for availability:

<CodeGroup>
  ```javascript JavaScript theme={null}
  async function getCalendars(userId, connectionId) {
    const response = await fetch(
      `https://api.meetergo.com/v4/calendar-connections/connections/${connectionId}/calendars`,
      {
        headers: {
          'Authorization': `Bearer ${process.env.MEETERGO_API_KEY}`,
          'x-meetergo-api-user-id': userId
        }
      }
    );
    return response.json();
  }
  ```

  ```bash cURL theme={null}
  curl -X GET "https://api.meetergo.com/v4/calendar-connections/connections/conn-123/calendars" \
    -H "Authorization: Bearer ak_live:uuid:secret" \
    -H "x-meetergo-api-user-id: user-uuid"
  ```
</CodeGroup>

**Response:**

```json theme={null}
{
  "calendars": [
    { "id": "primary", "name": "Calendar", "isPrimary": true, "canEdit": true, "selected": true },
    { "id": "work@group.calendar.google.com", "name": "Work", "isPrimary": false, "canEdit": true, "selected": false }
  ],
  "connectionId": "conn-123",
  "provider": "google"
}
```

### Update Calendar Connection

```
PATCH /v4/calendar-connections/connections/{connectionId}
```

Update settings for a connection such as which calendar to create events in, which calendars to check for availability, and notification preferences:

<CodeGroup>
  ```javascript JavaScript theme={null}
  async function updateConnection(userId, connectionId, settings) {
    const response = await fetch(
      `https://api.meetergo.com/v4/calendar-connections/connections/${connectionId}`,
      {
        method: 'PATCH',
        headers: {
          'Authorization': `Bearer ${process.env.MEETERGO_API_KEY}`,
          'x-meetergo-api-user-id': userId,
          'Content-Type': 'application/json'
        },
        body: JSON.stringify(settings)
      }
    );
    return response.json();
  }

  // Set which calendar events are created in
  await updateConnection(userId, connectionId, {
    primaryCalendar: 'work@group.calendar.google.com'
  });

  // Choose which calendars to check for availability
  await updateConnection(userId, connectionId, {
    selectedCalendars: ['primary', 'work@group.calendar.google.com']
  });
  ```

  ```bash cURL theme={null}
  curl -X PATCH "https://api.meetergo.com/v4/calendar-connections/connections/conn-123" \
    -H "Authorization: Bearer ak_live:uuid:secret" \
    -H "x-meetergo-api-user-id: user-uuid" \
    -H "Content-Type: application/json" \
    -d '{"primaryCalendar": "work@group.calendar.google.com"}'
  ```
</CodeGroup>

**Request body (all fields optional):**

| Field                         | Type      | Description                                        |
| ----------------------------- | --------- | -------------------------------------------------- |
| `primaryCalendar`             | string    | Calendar ID to create new events in                |
| `selectedCalendars`           | string\[] | Calendar IDs to check for availability conflicts   |
| `validateAvailability`        | boolean   | Whether to check this connection for conflicts     |
| `cancelIfDeclinedOrDeleted`   | boolean   | Auto-cancel booking if calendar event is declined  |
| `disableAttendeeNotification` | boolean   | Suppress attendee notifications on calendar events |

### Delete Calendar Connection

```
DELETE /v4/calendar-connections/connections/{connectionId}
```

<CodeGroup>
  ```javascript JavaScript theme={null}
  async function disconnectCalendar(userId, connectionId) {
    await fetch(
      `https://api.meetergo.com/v4/calendar-connections/connections/${connectionId}`,
      {
        method: 'DELETE',
        headers: {
          'Authorization': `Bearer ${process.env.MEETERGO_API_KEY}`,
          'x-meetergo-api-user-id': userId
        }
      }
    );
  }
  ```

  ```bash cURL theme={null}
  curl -X DELETE "https://api.meetergo.com/v4/calendar-connections/connections/conn-123" \
    -H "Authorization: Bearer ak_live:uuid:secret" \
    -H "x-meetergo-api-user-id: user-uuid"
  ```
</CodeGroup>

### Set Primary Calendar Connection

```
POST /v4/calendar-connections/connections/{connectionId}/set-primary
```

Set which calendar connection should be used for creating new events:

```javascript theme={null}
await fetch(
  `https://api.meetergo.com/v4/calendar-connections/connections/${connectionId}/set-primary`,
  {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${process.env.MEETERGO_API_KEY}`,
      'x-meetergo-api-user-id': userId
    }
  }
);
```

## Building Your Own UI

Here's a complete React example for a calendar settings page:

```jsx theme={null}
import { useState, useEffect } from 'react';

const PROVIDERS = [
  { id: 'google', name: 'Google Calendar', icon: '📅', color: '#4285F4' },
  { id: 'outlook', name: 'Microsoft 365', icon: '📆', color: '#0078D4' }
];

function CalendarSettings({ userId, apiKey }) {
  const [connections, setConnections] = useState([]);
  const [loading, setLoading] = useState(true);
  const [connecting, setConnecting] = useState(null);

  const headers = {
    'Authorization': `Bearer ${apiKey}`,
    'x-meetergo-api-user-id': userId
  };

  useEffect(() => {
    loadConnections();
    handleCallbackParams();
  }, []);

  async function loadConnections() {
    try {
      const response = await fetch(
        'https://api.meetergo.com/v4/calendar-connections/connections',
        { headers }
      );
      const data = await response.json();
      setConnections(data.connections);
    } finally {
      setLoading(false);
    }
  }

  function handleCallbackParams() {
    const params = new URLSearchParams(window.location.search);
    if (params.get('success') === 'true') {
      // Clear URL params and reload connections
      window.history.replaceState({}, '', window.location.pathname);
      loadConnections();
    }
  }

  async function connect(provider) {
    setConnecting(provider);
    try {
      const response = await fetch(
        `https://api.meetergo.com/v4/calendar-connections/auth/${provider}/url?` +
        new URLSearchParams({
          redirectUrl: window.location.href,
          setAsCalendarSync: 'true'
        }),
        { headers }
      );
      const { authUrl } = await response.json();
      window.location.href = authUrl;
    } catch (error) {
      alert('Failed to start connection. Please try again.');
      setConnecting(null);
    }
  }

  async function disconnect(connectionId) {
    if (!confirm('Disconnect this calendar?')) return;

    await fetch(
      `https://api.meetergo.com/v4/calendar-connections/connections/${connectionId}`,
      { method: 'DELETE', headers }
    );
    loadConnections();
  }

  if (loading) {
    return <div className="loading">Loading calendar connections...</div>;
  }

  return (
    <div className="calendar-settings">
      <h2>Calendar Connections</h2>
      <p className="description">
        Connect your calendar to automatically check for conflicts and
        create events when meetings are booked.
      </p>

      <div className="providers">
        {PROVIDERS.map(provider => {
          const connection = connections.find(c => c.provider === provider.id);
          const isConnecting = connecting === provider.id;

          return (
            <div key={provider.id} className="provider-card">
              <div className="provider-info">
                <span className="icon">{provider.icon}</span>
                <div>
                  <strong>{provider.name}</strong>
                  {connection && (
                    <span className="email">{connection.email}</span>
                  )}
                </div>
              </div>

              {connection ? (
                <div className="actions">
                  <span className="status connected">Connected</span>
                  <button
                    onClick={() => disconnect(connection.id)}
                    className="btn-secondary"
                  >
                    Disconnect
                  </button>
                </div>
              ) : (
                <button
                  onClick={() => connect(provider.id)}
                  disabled={isConnecting}
                  className="btn-primary"
                  style={{ backgroundColor: provider.color }}
                >
                  {isConnecting ? 'Connecting...' : 'Connect'}
                </button>
              )}
            </div>
          );
        })}
      </div>
    </div>
  );
}

export default CalendarSettings;
```

<Accordion title="CSS Styles">
  ```css theme={null}
  .calendar-settings {
    max-width: 600px;
    margin: 0 auto;
    padding: 24px;
  }

  .description {
    color: #666;
    margin-bottom: 24px;
  }

  .providers {
    display: flex;
    flex-direction: column;
    gap: 16px;
  }

  .provider-card {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 16px;
    border: 1px solid #e0e0e0;
    border-radius: 8px;
    background: #fff;
  }

  .provider-info {
    display: flex;
    align-items: center;
    gap: 12px;
  }

  .provider-info .icon {
    font-size: 24px;
  }

  .provider-info .email {
    display: block;
    font-size: 14px;
    color: #666;
  }

  .actions {
    display: flex;
    align-items: center;
    gap: 12px;
  }

  .status.connected {
    color: #22c55e;
    font-weight: 500;
  }

  .btn-primary {
    padding: 8px 16px;
    border: none;
    border-radius: 6px;
    color: white;
    font-weight: 500;
    cursor: pointer;
  }

  .btn-primary:disabled {
    opacity: 0.6;
    cursor: not-allowed;
  }

  .btn-secondary {
    padding: 8px 16px;
    border: 1px solid #e0e0e0;
    border-radius: 6px;
    background: white;
    cursor: pointer;
  }

  .btn-secondary:hover {
    background: #f5f5f5;
  }
  ```
</Accordion>

## Popup Window Flow

For a better UX, open OAuth in a popup window instead of redirecting:

```javascript theme={null}
function connectCalendarPopup(userId, provider, apiKey) {
  return new Promise(async (resolve, reject) => {
    // Get OAuth URL
    const response = await fetch(
      `https://api.meetergo.com/v4/calendar-connections/auth/${provider}/url?` +
      new URLSearchParams({
        redirectUrl: `${window.location.origin}/calendar-callback.html`
      }),
      {
        headers: {
          'Authorization': `Bearer ${apiKey}`,
          'x-meetergo-api-user-id': userId
        }
      }
    );
    const { authUrl } = await response.json();

    // Open popup
    const popup = window.open(
      authUrl,
      'calendar-connect',
      'width=600,height=700,left=100,top=100'
    );

    // Listen for callback message
    const handleMessage = (event) => {
      if (event.origin !== window.location.origin) return;

      if (event.data.type === 'calendar-connected') {
        window.removeEventListener('message', handleMessage);
        popup.close();

        if (event.data.success) {
          resolve(event.data.connectionId);
        } else {
          reject(new Error(event.data.error));
        }
      }
    };

    window.addEventListener('message', handleMessage);

    // Handle popup close
    const checkClosed = setInterval(() => {
      if (popup.closed) {
        clearInterval(checkClosed);
        window.removeEventListener('message', handleMessage);
        reject(new Error('Popup closed'));
      }
    }, 1000);
  });
}
```

**callback page (calendar-callback.html):**

```html theme={null}
<!DOCTYPE html>
<html>
<head><title>Connecting...</title></head>
<body>
  <script>
    const params = new URLSearchParams(window.location.search);
    window.opener.postMessage({
      type: 'calendar-connected',
      success: params.get('success') === 'true',
      connectionId: params.get('connectionId'),
      error: params.get('message') ? decodeURIComponent(params.get('message')) : null
    }, window.location.origin);
  </script>
</body>
</html>
```

## Error Handling

| Error                       | Cause                               | Solution                                              |
| --------------------------- | ----------------------------------- | ----------------------------------------------------- |
| `Connection already exists` | User already connected this account | Show message that account is already connected        |
| `access_denied`             | User declined OAuth permission      | Show message explaining why calendar access is needed |
| `invalid_scope`             | Missing required permissions        | Check OAuth configuration                             |
| `server_error`              | Provider API error                  | Retry after a few seconds                             |
| `user_not_found`            | Invalid `x-meetergo-api-user-id`    | Verify user exists in your meetergo account           |

## Best Practices

<Check>
  **Explain the benefits** - Tell users why connecting their calendar improves their experience
</Check>

<Check>
  **Handle errors gracefully** - OAuth can fail; show helpful messages and retry options
</Check>

<Check>
  **Show connection status** - Let users see which calendars are connected and manage them
</Check>

<Check>
  **Use popup flow** - Better UX than full-page redirect, keeps user context
</Check>

<Warning>
  **Never expose API keys in frontend code** - Make API calls from your backend, not the browser
</Warning>

<Warning>
  **Validate redirect URLs** - Only allow redirects to your own domain
</Warning>

## Next Steps

<CardGroup cols={2}>
  <Card title="Availability" icon="clock" href="/developer-docs/core-concepts/availability">
    Configure user availability schedules
  </Card>

  <Card title="Calendar Sync Recipe" icon="arrows-rotate" href="/developer-docs/recipes/calendar-sync">
    Sync bookings to external calendars via webhooks
  </Card>

  <Card title="Webhooks" icon="webhook" href="/developer-docs/webhooks/overview">
    Get notified when bookings are created or changed
  </Card>

  <Card title="API Reference" icon="code" href="/api-reference/calendar-authentication/get-oauth-authorization-url">
    Full API documentation
  </Card>
</CardGroup>
