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
| Provider | What Syncs | Config | Setup Flow |
|---|
| Google Drive | Documents from selected folders | folder_ids | Two-phase (pick folders 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) |
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.
Google Drive
Slack
Notion
Gmail
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"
}
result = client.connect_provider("slack", "YOUR_COLLECTION_ID")
print(result["auth_url"])
result = client.connect_provider("notion", "YOUR_COLLECTION_ID")
print(result["auth_url"])
result = client.connect_provider("gmail", "YOUR_COLLECTION_ID")
print(result["auth_url"])
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.
Google Drive Folders
Slack Channels
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..."]})
List available channels:channels = client.list_channels("CONNECTION_ID")
for ch in channels:
print(ch["id"], ch["name"], ch["num_members"], ch["is_selected"])
Response:[
{"id": "C01234ABC", "name": "general", "is_private": false, "num_members": 42, "is_selected": true},
{"id": "C56789DEF", "name": "engineering", "is_private": false, "num_members": 15, "is_selected": false}
]
Submit your selection:client.update_connection_config("CONNECTION_ID", {"channel_ids": ["C01234ABC", "C56789DEF"]})
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
| Status | Meaning |
|---|
pending | OAuth complete, waiting for folder/channel selection (Google Drive, Slack only) |
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.
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:
| 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 |
Slack
| Key | Type | Constraints | Description |
|---|
channel_ids | string[] | Max 50 items, must start with C or G, uppercase alphanumeric | Slack channel IDs to sync |
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 |
GET | /v1/connectors/{connection_id}/folders | Browse Google Drive folders |
GET | /v1/connectors/{connection_id}/channels | List Slack channels |
PATCH | /v1/connectors/{connection_id}/config | Update connection config |
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 |
PATCH /connectors/{connection_id}/config | EDIT on collection |
DELETE /connectors/{connection_id} | EDIT on collection |
GET /connectors/{connection_id}/folders | EDIT on collection |
GET /connectors/{connection_id}/channels | EDIT on collection |
/folders and /channels are GET endpoints but require EDIT permission because they access the provider’s API using stored credentials.
Next Steps