Skip to main content
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.
Connectors use OAuth, so users authorize access through the provider’s consent screen. Nebula never sees raw passwords.

Supported Providers

ProviderWhat SyncsConfigSetup Flow
Google DriveDocuments from selected foldersfolder_idsTwo-phase (pick folders after connecting)
SlackMessages from selected channelschannel_idsTwo-phase (pick channels after connecting)
GmailEmails from the authorized inboxOne-phase (syncs immediately)
NotionPages from the authorized workspaceOne-phase (syncs immediately)
Use the List Providers endpoint to check which connectors are enabled on your Nebula instance.
from nebula import Nebula

client = Nebula(api_key="YOUR_API_KEY")
providers = client.list_providers()
print(providers)  # ["gmail", "google_drive", "notion", "slack"]

Connecting a Data Source

Step 1: Start the OAuth Flow

Provide the collection_id where synced content should land. For Google Drive and Slack, you can optionally include config with pre-selected folder or channel IDs. If omitted, you’ll select them after authorization.
result = client.connect_provider("google_drive", "YOUR_COLLECTION_ID")
print(result["auth_url"])  # Redirect user here
Response:
{
  "auth_url": "https://accounts.google.com/o/oauth2/v2/auth?...",
  "state": "signed-state-token"
}

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 and Notion: Becomes active and syncing starts immediately.
  • Google Drive and Slack: Created with status pending. Proceed to step 3.

Step 3: Select Folders or Channels (Google Drive / Slack only)

After OAuth completes, browse the provider’s structure to find folder or channel IDs, then submit your selection.
Browse folders:
# List root folders
folders = client.list_folders("CONNECTION_ID")

# Browse a subfolder
subfolders = client.list_folders("CONNECTION_ID", parent_id="PARENT_FOLDER_ID")
for f in folders:
    print(f["id"], f["name"], f["is_selected"])
Response:
[
  {"id": "1ABC...", "name": "Project Docs", "has_children": true, "is_selected": true},
  {"id": "2DEF...", "name": "Research", "has_children": true, "is_selected": false}
]
Submit your selection:
client.update_connection_config("CONNECTION_ID", {"folder_ids": ["1ABC...", "2DEF..."]})
The config update sets the connection to active and triggers the initial sync automatically. The apply field defaults to "full_resync" — currently the only supported mode.

Listing Connections

View all connections for a collection. Connections with status revoked are excluded.
connections = client.list_connections("YOUR_COLLECTION_ID")
for conn in connections:
    print(conn["provider"], conn["status"], conn["health"])
Response:
[
  {
    "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..."]},
    "created_at": "2025-06-01T08:00:00Z",
    "health": "ok",
    "error_detail": null,
    "next_sync_at": "2025-06-15T14:30:00Z"
  }
]

Connection Statuses

StatusMeaning
pendingOAuth complete, waiting for folder/channel selection (Google Drive, Slack only)
activeConnected and syncing on schedule
revokedDisconnected, hidden from list results

Enriched Fields

FieldTypeDescription
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} | nullStructured version of last_error. retryable is true for all current error types
next_sync_atstring | nullEstimated next sync time. null for non-active connections. Equal to created_at if never synced

Get Connection Details

Fetch a single connection by ID.
conn = client.get_connection("CONNECTION_ID")
print(conn["health"], conn["next_sync_at"])
Response:
{
  "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..."]},
  "created_at": "2025-06-01T08:00:00Z",
  "health": "ok",
  "error_detail": null,
  "next_sync_at": "2025-06-15T14:30:00Z"
}

Updating Connection Config

Change which folders or channels a connection syncs. This resets the sync cursor and triggers a fresh sync.
client.update_connection_config("CONNECTION_ID", {"folder_ids": ["NEW_FOLDER_ID"]})
The apply field defaults to "full_resync". Unsupported values return a 400 error:
{"detail": "Unsupported apply mode: 'incremental'. Supported: ['full_resync']"}
Changing folders or channels resets the sync cursor. Nebula will re-sync all content from the new selection.

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.
# Simple disconnect (keep memories)
client.disconnect("CONNECTION_ID")

# Disconnect and delete synced memories
result = client.disconnect("CONNECTION_ID", delete_memories=True)
print(result["warnings"])  # [] on success
Success response:
{
  "message": "Connection CONNECTION_UUID disconnected",
  "warnings": []
}
Partial cleanup response:
{
  "message": "Connection CONNECTION_UUID disconnected",
  "warnings": [
    {
      "code": "cleanup_partial",
      "message": "Some memories could not be deleted. For guaranteed cleanup, delete the entire collection."
    }
  ]
}
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.

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.
result = client.trigger_sync("CONNECTION_ID")
print(result["message"])  # "Sync triggered"
The endpoint returns immediately. The sync runs asynchronously in the background. Error responses:
StatusCondition
400Connection is not active (e.g. pending or revoked)
403User lacks EDIT permission on the collection
409Sync already in progress for this connection

Config Reference

Google Drive

KeyTypeConstraintsDescription
folder_idsstring[]Max 10 items, 64 chars each, alphanumeric onlyDrive folder IDs to sync

Slack

KeyTypeConstraintsDescription
channel_idsstring[]Max 50 items, must start with C or G, uppercase alphanumericSlack channel IDs to sync

API Reference

MethodEndpointDescription
GET/v1/connectors/providersList available connector providers
POST/v1/connectors/{provider}/connectStart OAuth flow
GET/v1/connectorsList connections for a collection
GET/v1/connectors/{connection_id}Get a single connection
POST/v1/connectors/{connection_id}/syncTrigger manual sync
GET/v1/connectors/{connection_id}/foldersBrowse Google Drive folders
GET/v1/connectors/{connection_id}/channelsList Slack channels
PATCH/v1/connectors/{connection_id}/configUpdate connection config
DELETE/v1/connectors/{connection_id}Disconnect a data source
All endpoints require authentication via Authorization: Bearer YOUR_API_KEY.

Permissions

EndpointPermission
GET /connectors/providersAuthenticated (no collection check)
GET /connectorsRead access to collection
GET /connectors/{connection_id}Read access to collection
POST /connectors/{provider}/connectEDIT on collection
POST /connectors/{connection_id}/syncEDIT on collection
PATCH /connectors/{connection_id}/configEDIT on collection
DELETE /connectors/{connection_id}EDIT on collection
GET /connectors/{connection_id}/foldersEDIT on collection
GET /connectors/{connection_id}/channelsEDIT on collection
/folders and /channels are GET endpoints but require EDIT permission because they access the provider’s API using stored credentials.

Next Steps