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

# Google Calendar

> Connect a personal or Google Workspace calendar account over OAuth, choose the calendars to cover, and sync their events into a dataset of that account's own.

Connect one **Google account** — personal or Workspace — to Cognee through a per-user OAuth consent, choose which calendars to cover, and have their events become searchable memory in a dataset of that account's own.

<Info>
  This integration is for self-hosted Cognee deployments — you create and own the Google Cloud OAuth client. It is the server-side, multi-user counterpart to calling [`google_calendar_source()`](/integrations/dlt-integration#google-calendar-connector) from Python yourself: the same connector runs underneath, but Cognee holds the OAuth credential instead of a local token file.
</Info>

**Available from `v1.6.5`.** See the [changelog](/changelog).

## What It Does

* **One Google account, one Cognee user.** Whoever completes the consent owns the credential, and the account's events are indexed into that user's memory. Personal and Workspace accounts take the same path and are told apart by the hosted-domain claim (`account_type: workspace` or `personal`).
* **Read-only.** The requested scopes are `openid email https://www.googleapis.com/auth/calendar.readonly` — Cognee never creates, changes or deletes events.
* **Its own grant.** Like [Google Drive](/integrations/google-drive-integration) and [Gmail](/integrations/gmail-integration), Google Calendar is a separate provider with its own settings and its own consent, so disconnecting one never revokes the others.
* **Opt-in by calendar.** A new connection selects nothing, and there is no "all calendars" selection. No events are read until you choose calendars explicitly and start a sync.
* **Incremental and deletion-aware.** Each calendar keeps its own sync token; later syncs fetch only what changed, and deleted events — as well as every event of a calendar you deselect — are forgotten.

## Prerequisites

* A Cognee backend on `v1.6.5` or later.
* The **`google-calendar` extra** installed on that backend:

  ```bash theme={null}
  pip install "cognee[google-calendar]"
  ```

  It pulls in `google-api-python-client`, `google-auth` and `google-auth-oauthlib`, which the sync builds its Calendar client with.
* A **Google Cloud project** with the **Google Calendar API** enabled.

## Setup

### 1. Create the Google Cloud OAuth client

In the [Google Cloud Console](https://console.cloud.google.com/), enable the Google Calendar API, then:

| Step | Value |
| - | - |
| **Branding** and **Audience** | Configure the consent screen for your audience. While the app is in **Testing**, add every account that will connect under **Audience → Test users**. |
| **Data Access → Add or remove scopes** | `openid`, `email` (listed in the picker as `.../auth/userinfo.email`), and `https://www.googleapis.com/auth/calendar.readonly`. |
| **Clients → Create client** | Application type **Web application**. |
| **Authorized redirect URIs** | `https://<your-host>/api/v1/integrations/google_calendar/callback` — must match `GOOGLE_CALENDAR_REDIRECT_URI` verbatim, with no trailing `/`. Leave **Authorized JavaScript origins** empty: it accepts only a bare origin and rejects this path. |

<Warning>
  `calendar.readonly` is a **sensitive scope**. Publishing the app to the public requires Google's OAuth verification. Test users work without verification.
</Warning>

### 2. Configure Cognee

Add the client's values to your backend's `.env` (the same block is in `.env.template`):

| Variable | Value |
| - | - |
| `GOOGLE_CALENDAR_CLIENT_ID` | From the OAuth client. |
| `GOOGLE_CALENDAR_CLIENT_SECRET` | From the OAuth client. |
| `GOOGLE_CALENDAR_STATE_SECRET` | Any long random string. It signs the OAuth `state` parameter. Use a different value from the Drive and Gmail state secrets. |
| `GOOGLE_CALENDAR_REDIRECT_URI` | The callback registered on the client, e.g. `http://localhost:8000/api/v1/integrations/google_calendar/callback`. |
| `GOOGLE_CALENDAR_FRONTEND_BASE_URL` | Your frontend's origin, e.g. `http://localhost:3000` — where the callback sends the browser afterwards. |

The SDK connector reads the same `GOOGLE_CALENDAR_*` prefix for local use (`GOOGLE_CALENDAR_IDS`, `GOOGLE_CALENDAR_REFRESH_TOKEN` and the other fields listed on the [connector page](/integrations/dlt-integration#google-calendar-connector)). Hosted syncs never use those fields: a connected account syncs only the calendars its user selected, never a server-wide `GOOGLE_CALENDAR_IDS` list.

Stored tokens are encrypted with the integrations framework's credential key, the same `INTEGRATION_CREDENTIALS_KEY` (or `INTEGRATION_CREDENTIALS_KEYS` keyring) described on the [Google Drive](/integrations/google-drive-integration#2-configure-cognee) page.

The settings are checked when they are used, not at startup, so a backend without them still boots. `POST /authorize` answers **503** (`google_calendar integration is not configured on this server.`) until they are set. Restart the backend after changing `.env`.

### 3. Connect

```bash theme={null}
curl -X POST https://<your-host>/api/v1/integrations/google_calendar/authorize \
  -H "Authorization: Bearer <your-cognee-token>"
# {"authorizeUrl":"https://accounts.google.com/o/oauth2/v2/auth?...&access_type=offline&prompt=consent"}
```

The connect flow is the same one Google Drive uses: finish it in the browser that called `/authorize`, within 10 minutes, and read the result from the redirect to `<GOOGLE_CALENDAR_FRONTEND_BASE_URL>/integrations?google_calendar=<outcome>`. The outcomes (`connected`, `cancelled`, `error_invalid_state`, `error_already_connected`, `error_exchange_failed`) and the split-domain cookie caveat are described under [Google Drive → Connect](/integrations/google-drive-integration#3-connect).

Calendar selection is driven through the API calls below.

## Choosing Calendars

List the account's calendars:

```bash theme={null}
curl https://<your-host>/api/v1/integrations/google_calendar/resources \
  -H "Authorization: Bearer <your-cognee-token>"
```

Each entry carries the calendar `id`, its `name` (your own name for the calendar if you set one, otherwise its title), its `description`, and `attributes` with `primary` (whether it is the account's primary calendar) and `access_role` (Google's access role for the account, e.g. `owner` or `reader`). Persist a selection with `PUT`:

```bash theme={null}
curl -X PUT https://<your-host>/api/v1/integrations/google_calendar/resources \
  -H "Authorization: Bearer <your-cognee-token>" \
  -H "Content-Type: application/json" \
  -d '{"resourceIds": ["jane@example.com", "team@group.calendar.google.com"]}'
```

Unlike Gmail and Google Drive, the selection has only two states:

| `resourceIds` | Meaning |
| - | - |
| `[]` | Nothing. A newly connected account starts here. |
| `["<calendar id>", …]` | Events from exactly those calendars. |

`null` is refused with **422** (`google_calendar needs an explicit list of resource ids; null is not supported`): the connector reads a missing selection as the primary calendar or the server's `GOOGLE_CALENDAR_IDS`, so "all calendars" has no safe meaning per user. A blank or whitespace-only id is refused with **422** as well (`Resource ids must not be blank or whitespace only`). Duplicate ids are stored once.

**Narrowing the selection deletes data.** The next sync forgets every event of a calendar you dropped from `resourceIds`, from the graph, vector and relational stores alike. Setting the selection to `[]` and syncing clears everything the account has indexed; so does [disconnecting with `delete_data=true`](#token-lifecycle-and-disconnect). A sync in which every calendar fails forgets nothing, but once any selected calendar finishes, the dropped calendars are forgotten even if another calendar failed.

## Syncing and Status

Connecting does not start a sync. Start one after choosing calendars:

```bash theme={null}
curl -X POST https://<your-host>/api/v1/integrations/google_calendar/sync \
  -H "Authorization: Bearer <your-cognee-token>"
# {"accepted": true}
```

The sync runs **detached**, and the response doesn't carry its result. All selected calendars go through one `google_calendar_source()` handed to [`remember()`](/python-api/remember) with `write_disposition="merge"`, `primary_key="id"` and no row cap, so a re-sync updates events instead of duplicating them. It runs with `self_improvement=False`, so no whole-graph [`improve()`](/python-api/improve) pass fires on each refresh. Each event becomes one document with the calendar name, organizer and attendees written in, so the same person is linked across meetings.

* **Only the first sync reads each calendar in full.** The connector keeps one sync token per selected calendar; later syncs ask Google only for what changed. A calendar whose token has expired is listed again in full and reconciled against what was stored. Adding a calendar to the selection backfills just that calendar.
* **Deletions are forgotten.** A deleted event is removed from memory on the next sync. A calendar that was synced before and has since left the account's calendar list (deleted or no longer shared) has its events forgotten too.
* **One failing calendar doesn't block the others.** When one calendar fails, the others still finish and save their sync tokens; the failed one keeps its previous state and is retried on the next sync. An authentication error or a rate limit ends the whole run, since the remaining calendars would fail the same way.
* **One sync per account at a time.** While a sync is running, another `/sync` call returns `{"accepted": false}` and does nothing. The guard lives in the API process, so a deployment with several workers needs its own queue or lock.
* **Nothing refreshes on its own.** There are no Calendar webhooks and no built-in scheduler. Call `/sync` again, or from your own scheduler, to pick up new events.

Events land in **one dataset per connected account**, named `google_calendar_<email-slug>_<hash>`. The slug is built the same way as for [Google Drive datasets](/integrations/google-drive-integration#syncing-and-status); the hash is the first 10 hex characters of the SHA-256 of the Google account id.

```python theme={null}
import cognee

results = await cognee.recall(
    "When did I last meet the vendor, and who attended?",
    datasets=["google_calendar_jane_example_com_3f2a1b9c4d"],
)
```

`GET /api/v1/integrations/google_calendar/connection` returns the same fields as the [Drive connection state](/integrations/google-drive-integration#reading-the-connection-state): `connected`, `accountLabel`, `datasetId`, `storedItems`, `syncStatus` (`ok`, `degraded`, `syncing`), `lastSyncedAt` and `syncCounts`. Calendar's `syncCounts` carry `scanned`, `skipped` (`workingLocation` events, which are not ingested), `failed`, `deleted` and `calendars` (how many calendars finished), plus `failed_auth`, `failed_rate_limit`, `failed_calendar_sync` and `failed_ingestion` when something failed.

Reconnecting an account keeps its stored calendar selection. Only a first-time connection starts with `[]`.

The repository's `examples/guides/google_integration_sync.py` drives these calls for you once an account is connected:

```bash theme={null}
# List calendars and the connection status
uv run python examples/guides/google_integration_sync.py google_calendar

# Select a calendar and start a sync
uv run python examples/guides/google_integration_sync.py google_calendar --resource-id CALENDAR_ID
```

## Token Lifecycle and Disconnect

Tokens are handled exactly as for [Google Drive](/integrations/google-drive-integration#token-lifecycle): the access token is refreshed 5 minutes before it expires, and an `invalid_grant` on refresh (access revoked at [myaccount.google.com/permissions](https://myaccount.google.com/permissions)) revokes the connection locally so it stops reading as connected.

```bash theme={null}
# Revoke the connection, keep the indexed events
curl -X DELETE "https://<your-host>/api/v1/integrations/google_calendar/connection" \
  -H "Authorization: Bearer <your-cognee-token>"

# Revoke it and forget the account's google_calendar_… dataset as well
curl -X DELETE "https://<your-host>/api/v1/integrations/google_calendar/connection?delete_data=true" \
  -H "Authorization: Bearer <your-cognee-token>"
```

Disconnecting revokes the local credential and makes a best-effort call to revoke the token at Google. If the dataset deletion fails, the endpoint answers **502** (`google_calendar disconnected, but its data could not be deleted`). The connection is already gone at that point.

## Limitations

* **No "all calendars" selection.** List the calendars you want explicitly; a calendar added to the account later is not picked up until you add it to the selection.
* **No automatic refresh.** `/sync` is the only thing that updates memory.
* **One Cognee user per Google account.** Connecting an account that another user already holds is refused with `error_already_connected`.
* **Attachments and conference details** beyond the meeting link are not ingested.
* **`calendar.readonly` is a sensitive scope**, so public distribution needs Google's OAuth verification.

## Related

<CardGroup cols={2}>
  <Card title="Gmail" icon="mail" href="/integrations/gmail-integration">
    The sibling Google OAuth integration for mailboxes, selected by label.
  </Card>

  <Card title="Google Drive" icon="hard-drive" href="/integrations/google-drive-integration">
    The sibling Google OAuth integration for files, with the same connect flow, status fields and token handling.
  </Card>

  <Card title="Calendar from Python" icon="download" href="/integrations/dlt-integration#google-calendar-connector">
    Call `google_calendar_source()` directly with your own OAuth token, without the integrations API.
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.