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

# Connectors Guide

> Sync external data sources into Nebula collections via OAuth

Connectors let you link external data sources to a Nebula collection. Once connected, files and messages sync automatically into your memories on a recurring schedule.

<Note>Connectors use OAuth, so users authorize access through the provider's consent screen. Nebula never sees raw passwords.</Note>

Google and Microsoft 365 connectors require a workspace OAuth app. A workspace owner or admin can provide the app's client ID and secret on the first connect; Nebula stores the app once for the workspace, and later users connect with `oauth_client_mode: "workspace"` without seeing the secret.

## Supported Providers

| Provider         | What Syncs                                      | Config                                                    | Setup Flow                                      |
| ---------------- | ----------------------------------------------- | --------------------------------------------------------- | ----------------------------------------------- |
| **Google Drive** | Documents from selected folders or files        | `folder_ids`, `file_ids`                                  | Two-phase (pick folders/files after connecting) |
| **Slack**        | Messages from selected channels                 | `channel_ids`                                             | Two-phase (pick channels after connecting)      |
| **Gmail**        | Emails from the authorized inbox                | —                                                         | One-phase (syncs immediately)                   |
| **Notion**       | Pages from the authorized workspace             | —                                                         | One-phase (syncs immediately)                   |
| **Outlook**      | Emails from Microsoft 365 Outlook               | `folder_ids`                                              | One-phase (syncs immediately)                   |
| **OneDrive**     | Documents from selected folders or files        | `folder_ids`, `file_ids`                                  | Two-phase (pick folders/files after connecting) |
| **SharePoint**   | Files from selected document libraries          | `drive_ids`, `folder_ids`, `file_ids`                     | Two-phase (pick libraries after connecting)     |
| **Teams**        | Messages from selected Teams channels and chats | `team_channel_ids`, `chat_ids`, `include_channel_replies` | Two-phase (pick sources after connecting)       |

<Info>Use the **List Providers** endpoint to check which connectors are enabled on your Nebula instance.</Info>

<CodeGroup>
  ```python Python theme={null}
  from nebula import Nebula

  client = Nebula(api_key="YOUR_API_KEY")
  providers = client.connectors.list_providers().results
  print(providers)  # ["gmail", "google_drive", "notion", "onedrive", "outlook", "sharepoint", "slack", "teams"]
  ```

  ```javascript JavaScript theme={null}
  import Nebula from '@nebula-ai/sdk';

  const client = new Nebula({ apiKey: 'YOUR_API_KEY' });
  const { results: providers } = await client.connectors.listProviders();
  console.log(providers); // ["gmail", "google_drive", "notion", "onedrive", "outlook", "sharepoint", "slack", "teams"]
  ```

  ```bash cURL theme={null}
  curl -X GET "https://api.zeroset.com/v1/connectors/providers" \
    -H "Authorization: Bearer YOUR_API_KEY"

  # Response: ["gmail", "google_drive", "notion", "onedrive", "outlook", "sharepoint", "slack", "teams"]
  ```
</CodeGroup>

## Connecting a Data Source

### Step 1: Start the OAuth Flow

Provide the `collection_id` where synced content should land. For a workspace's first Google or Microsoft 365 connect, a workspace owner or admin includes `oauth_client_id` and `oauth_client_secret`; Nebula saves that app at the workspace level. Later connects use `oauth_client_mode: "workspace"`. For Google Drive, Slack, OneDrive, SharePoint, and Teams, you can optionally include `config` with pre-selected folder, file, or channel IDs. If omitted, you'll select them after authorization.

<Tabs>
  <Tab title="Google Drive">
    <CodeGroup>
      ```python Python theme={null}
      result = client.connectors.connect(
          "google_drive",
          collection_id="YOUR_COLLECTION_ID",
          oauth_client_mode="workspace",
      ).results
      print(result.auth_url)  # Redirect user here
      ```

      ```javascript JavaScript theme={null}
      const { results: result } = await client.connectors.connect('google_drive', {
        collection_id: 'YOUR_COLLECTION_ID',
        oauth_client_mode: 'workspace',
      });
      console.log(result.auth_url); // Redirect user here
      ```

      ```bash cURL theme={null}
      curl -X POST "https://api.zeroset.com/v1/connectors/google_drive/connect" \
        -H "Content-Type: application/json" \
        -H "Authorization: Bearer YOUR_API_KEY" \
        -d '{
          "collection_id": "YOUR_COLLECTION_ID",
          "oauth_client_mode": "workspace"
        }'
      ```
    </CodeGroup>

    Response:

    ```json theme={null}
    {
      "auth_url": "https://accounts.google.com/o/oauth2/v2/auth?...",
      "state": "signed-state-token"
    }
    ```
  </Tab>

  <Tab title="Slack">
    <CodeGroup>
      ```python Python theme={null}
      result = client.connectors.connect("slack", collection_id="YOUR_COLLECTION_ID").results
      print(result.auth_url)
      ```

      ```javascript JavaScript theme={null}
      const { results: result } = await client.connectors.connect('slack', {
        collection_id: 'YOUR_COLLECTION_ID',
      });
      console.log(result.auth_url);
      ```

      ```bash cURL theme={null}
      curl -X POST "https://api.zeroset.com/v1/connectors/slack/connect" \
        -H "Content-Type: application/json" \
        -H "Authorization: Bearer YOUR_API_KEY" \
        -d '{
          "collection_id": "YOUR_COLLECTION_ID"
        }'
      ```
    </CodeGroup>
  </Tab>

  <Tab title="Notion">
    <CodeGroup>
      ```python Python theme={null}
      result = client.connectors.connect("notion", collection_id="YOUR_COLLECTION_ID").results
      print(result.auth_url)
      ```

      ```javascript JavaScript theme={null}
      const { results: result } = await client.connectors.connect('notion', {
        collection_id: 'YOUR_COLLECTION_ID',
      });
      console.log(result.auth_url);
      ```

      ```bash cURL theme={null}
      curl -X POST "https://api.zeroset.com/v1/connectors/notion/connect" \
        -H "Content-Type: application/json" \
        -H "Authorization: Bearer YOUR_API_KEY" \
        -d '{
          "collection_id": "YOUR_COLLECTION_ID"
        }'
      ```
    </CodeGroup>
  </Tab>

  <Tab title="Gmail">
    <CodeGroup>
      ```python Python theme={null}
      result = client.connectors.connect(
          "gmail",
          collection_id="YOUR_COLLECTION_ID",
          oauth_client_mode="workspace",
      ).results
      print(result.auth_url)
      ```

      ```javascript JavaScript theme={null}
      const { results: result } = await client.connectors.connect('gmail', {
        collection_id: 'YOUR_COLLECTION_ID',
        oauth_client_mode: 'workspace',
      });
      console.log(result.auth_url);
      ```

      ```bash cURL theme={null}
      curl -X POST "https://api.zeroset.com/v1/connectors/gmail/connect" \
        -H "Content-Type: application/json" \
        -H "Authorization: Bearer YOUR_API_KEY" \
        -d '{
          "collection_id": "YOUR_COLLECTION_ID",
          "oauth_client_mode": "workspace"
        }'
      ```
    </CodeGroup>
  </Tab>
</Tabs>

### Step 2: User Authorizes

Redirect the user to the `auth_url` from the response. After they approve access on the provider's consent screen, Nebula handles the OAuth callback automatically.

* **Gmail, Notion, and Outlook**: Becomes `active` and syncing starts immediately. Outlook syncs Inbox, Sent Items, and Archive by default; pass `folder_ids` at connect time to use a custom folder set.
* **Google Drive, Slack, OneDrive, SharePoint, and Teams**: Created with status `pending`. Proceed to step 3.

<Note>Gmail connections that use a workspace Google OAuth app sync on the normal polling cadence unless a workspace owner or admin configures a Pub/Sub topic and push service account for the workspace app.</Note>

### Step 3: Complete Provider Selection

Google Drive, Slack, OneDrive, SharePoint, and Teams connections require a folder, file, library, channel, or chat selection before syncing. Complete that selection in the Nebula app's connector settings UI after OAuth returns.

Once the selection is saved, the connection becomes `active` and the initial sync starts automatically.

## Listing Connections

View all connections for a collection. Connections with status `revoked` are excluded.

<CodeGroup>
  ```python Python theme={null}
  connections = client.connectors.list(collection_id="YOUR_COLLECTION_ID").results
  for conn in connections:
      print(conn.provider, conn.status, conn.health)
  ```

  ```javascript JavaScript theme={null}
  const { results: connections } = await client.connectors.list({
    collection_id: 'YOUR_COLLECTION_ID',
  });
  connections.forEach(conn => console.log(conn.provider, conn.status, conn.health));
  ```

  ```bash cURL theme={null}
  curl -X GET "https://api.zeroset.com/v1/connectors?collection_id=YOUR_COLLECTION_ID" \
    -H "Authorization: Bearer YOUR_API_KEY"
  ```
</CodeGroup>

Response:

```json theme={null}
[
  {
    "id": "CONNECTION_UUID",
    "provider": "google_drive",
    "external_account_id": "user@gmail.com",
    "status": "active",
    "collection_id": "COLLECTION_UUID",
    "items_synced": 47,
    "last_synced_at": "2025-06-15T10:30:00Z",
    "last_error": null,
    "config": {"folder_ids": ["1ABC..."], "file_ids": ["1XYZ..."]},
    "created_at": "2025-06-01T08:00:00Z",
    "health": "ok",
    "error_detail": null,
    "next_sync_at": "2025-06-15T14:30:00Z"
  }
]
```

### Connection Statuses

| Status    | Meaning                                                                                                                  |
| --------- | ------------------------------------------------------------------------------------------------------------------------ |
| `pending` | OAuth complete, waiting for required content selection (for example Google Drive, OneDrive, SharePoint, Slack, or Teams) |
| `active`  | Connected and syncing on schedule                                                                                        |
| `revoked` | Disconnected, hidden from list results                                                                                   |

### Enriched Fields

| Field          | Type                           | Description                                                                                            |
| -------------- | ------------------------------ | ------------------------------------------------------------------------------------------------------ |
| `health`       | `"ok" \| "error" \| null`      | `"ok"` if active with no errors, `"error"` if active with `last_error`, `null` for non-active statuses |
| `error_detail` | `{message, retryable} \| null` | Structured version of `last_error`. `retryable` is `true` for all current error types                  |
| `next_sync_at` | `string \| null`               | Estimated next sync time. `null` for non-active connections. Equal to `created_at` if never synced     |

## Get Connection Details

Fetch a single connection by ID.

<CodeGroup>
  ```python Python theme={null}
  conn = client.connectors.retrieve("CONNECTION_ID").results
  print(conn.health, conn.next_sync_at)
  ```

  ```javascript JavaScript theme={null}
  const { results: conn } = await client.connectors.retrieve('CONNECTION_ID');
  console.log(conn.health, conn.next_sync_at);
  ```

  ```bash cURL theme={null}
  curl -X GET "https://api.zeroset.com/v1/connectors/CONNECTION_ID" \
    -H "Authorization: Bearer YOUR_API_KEY"
  ```
</CodeGroup>

Response:

```json theme={null}
{
  "id": "CONNECTION_UUID",
  "provider": "google_drive",
  "external_account_id": "user@gmail.com",
  "status": "active",
  "collection_id": "COLLECTION_UUID",
  "items_synced": 47,
  "last_synced_at": "2025-06-15T10:30:00Z",
  "last_error": null,
  "config": {"folder_ids": ["1ABC..."], "file_ids": ["1XYZ..."]},
  "created_at": "2025-06-01T08:00:00Z",
  "health": "ok",
  "error_detail": null,
  "next_sync_at": "2025-06-15T14:30:00Z"
}
```

## Disconnecting

Remove a connection and stop syncing. Nebula attempts to revoke the OAuth token with the provider. Previously synced memories remain in the collection unless `delete_memories` is set to `true`.

<CodeGroup>
  ```python Python theme={null}
  # Simple disconnect (keep memories)
  client.connectors.disconnect("CONNECTION_ID")

  # Disconnect and delete synced memories
  result = client.connectors.disconnect("CONNECTION_ID", delete_memories=True).results
  print(result.warnings)  # [] on success
  ```

  ```javascript JavaScript theme={null}
  // Simple disconnect (keep memories)
  await client.connectors.disconnect('CONNECTION_ID');

  // Disconnect and delete synced memories
  const { results: result } = await client.connectors.disconnect('CONNECTION_ID', {
    delete_memories: true,
  });
  console.log(result.warnings); // [] on success
  ```

  ```bash cURL theme={null}
  # Simple disconnect (keep memories)
  curl -X DELETE "https://api.zeroset.com/v1/connectors/CONNECTION_ID" \
    -H "Authorization: Bearer YOUR_API_KEY"

  # Disconnect and delete synced memories
  curl -X DELETE "https://api.zeroset.com/v1/connectors/CONNECTION_ID?delete_memories=true" \
    -H "Authorization: Bearer YOUR_API_KEY"
  ```
</CodeGroup>

Success response:

```json theme={null}
{
  "message": "Connection CONNECTION_UUID disconnected",
  "warnings": []
}
```

Partial cleanup response:

```json theme={null}
{
  "message": "Connection CONNECTION_UUID disconnected",
  "warnings": [
    {
      "code": "cleanup_partial",
      "message": "Some memories could not be deleted. For guaranteed cleanup, delete the entire collection."
    }
  ]
}
```

<Warning>Memory deletion is best-effort. Memories created before connection tracking was added,
or whose mapping was overwritten by content updates, may not be captured. If a lock is held
(for example, sync or config update in progress), memory cleanup is skipped. In rare cases,
an unusually slow deletion operation may cause the internal lock to expire, allowing concurrent
operations to proceed before cleanup finishes. Metadata-based attribution may also affect
engrams with manually crafted matching metadata in the same collection. For guaranteed
cleanup, delete the entire collection.</Warning>

## Sync Behavior

* **Schedule**: Active connections sync automatically every 4 hours (configurable by the instance admin).
* **Incremental**: After the initial full sync, only new or changed items are fetched.
* **Deduplication**: Items are tracked by their provider-specific ID and content hash. Unchanged items are skipped.
* **Retries**: Failed items are retried up to 3 times. After that, they enter a cooldown period before being re-attempted.

## Manual Sync

Trigger an immediate sync for a connection without waiting for the next scheduled run.

<CodeGroup>
  ```python Python theme={null}
  result = client.connectors.sync("CONNECTION_ID").results
  print(result.message)  # "Sync triggered"
  ```

  ```javascript JavaScript theme={null}
  const { results: result } = await client.connectors.sync('CONNECTION_ID');
  console.log(result.message); // "Sync triggered"
  ```

  ```bash cURL theme={null}
  curl -X POST "https://api.zeroset.com/v1/connectors/CONNECTION_ID/sync" \
    -H "Authorization: Bearer YOUR_API_KEY"
  ```
</CodeGroup>

The endpoint returns immediately. The sync runs asynchronously in the background.

**Error responses:**

| Status | Condition                                                |
| ------ | -------------------------------------------------------- |
| 400    | Connection is not `active` (e.g. `pending` or `revoked`) |
| 403    | User lacks EDIT permission on the collection             |
| 409    | Sync already in progress for this connection             |

## Config Reference

### Google Drive

| Key          | Type       | Constraints                                                 | Description              |
| ------------ | ---------- | ----------------------------------------------------------- | ------------------------ |
| `folder_ids` | `string[]` | Max 10 items, 64 chars each, alphanumeric only              | Drive folder IDs to sync |
| `file_ids`   | `string[]` | Max 20 items, 128 chars each, alphanumeric plus `_` and `-` | Drive file IDs to sync   |

### Slack

| Key           | Type       | Constraints                                                      | Description               |
| ------------- | ---------- | ---------------------------------------------------------------- | ------------------------- |
| `channel_ids` | `string[]` | Max 50 items, must start with `C` or `G`, uppercase alphanumeric | Slack channel IDs to sync |

### Outlook

| Key          | Type       | Constraints                   | Description                                                                                |
| ------------ | ---------- | ----------------------------- | ------------------------------------------------------------------------------------------ |
| `folder_ids` | `string[]` | Max 100 items, 512 chars each | Optional Microsoft Graph mail folder IDs to sync instead of Inbox, Sent Items, and Archive |

## API Reference

| Method   | Endpoint                              | Description                        |
| -------- | ------------------------------------- | ---------------------------------- |
| `GET`    | `/v1/connectors/providers`            | List available connector providers |
| `POST`   | `/v1/connectors/{provider}/connect`   | Start OAuth flow                   |
| `GET`    | `/v1/connectors`                      | List connections for a collection  |
| `GET`    | `/v1/connectors/{connection_id}`      | Get a single connection            |
| `POST`   | `/v1/connectors/{connection_id}/sync` | Trigger manual sync                |
| `DELETE` | `/v1/connectors/{connection_id}`      | Disconnect a data source           |

All endpoints require authentication via `Authorization: Bearer YOUR_API_KEY`.

### Permissions

| Endpoint                                | Permission                          |
| --------------------------------------- | ----------------------------------- |
| `GET /connectors/providers`             | Authenticated (no collection check) |
| `GET /connectors`                       | Read access to collection           |
| `GET /connectors/{connection_id}`       | Read access to collection           |
| `POST /connectors/{provider}/connect`   | EDIT on collection                  |
| `POST /connectors/{connection_id}/sync` | EDIT on collection                  |
| `DELETE /connectors/{connection_id}`    | EDIT on collection                  |

## Next Steps

* [Collections](/guides/collections) - Organize memories into collections
* [Memory Operations](/guides/memory-operations) - Work with synced memories
* [Search](/guides/search) - Search across synced content
