Skip to documentation

Intake

Connect Confluence, SharePoint & Slack

This guide wires an external source into the knowledge base as a content connector: create the source with credentials, run the first full backfill, and let incremental sync keep it fresh. Everything it ingests becomes documents you can search and cite exactly like a posted one.

Prerequisites

  • A key with kb:write (create/sync a source) and data:read (list sources), exported as $ELI_KEY. See Getting started.
  • Owner/admin on the workspace — creating connectors and storing credentials is owner/admin-gated.
  • The per-source credential below, from the upstream system's admin.

Per-source setup

Confluence

Create an API token for a read-only service account, and note the space keys to sync and your base URL. The connector polls by lastModified via CQL and paginates with the v2 cursor, so incremental syncs only pull pages edited since the last watermark.

confluence configjson
{
  "type": "confluence",
  "config": {
    "baseUrl": "https://acme.atlassian.net/wiki",
    "spaceKeys": ["ENG", "OPS"]
  },
  "credential": {
    "email": "svc-eli@acme.com",
    "apiToken": "<atlassian-api-token>"
  }
}

SharePoint

Register an Entra ID (Azure AD) app with the application Microsoft Graph permission Sites.Selected and grant tenant admin consent. That permission starts with access to no sites: a SharePoint/Graph administrator must separately grant the connector app read on every site id you configure. The connector uses the Graph driveItem delta query and stores the opaque deltaLink in sync_state, so each incremental call returns exactly what changed — additions, edits, and deletes.

one-time Sites.Selected grant per sitehttp
POST https://graph.microsoft.com/v1.0/sites/<site-id>/permissions
Authorization: Bearer <site-grant-admin-token>
Content-Type: application/json

{
  "roles": ["read"],
  "grantedToIdentities": [{
    "application": {
      "id": "<app-client-id>",
      "displayName": "eli.ai connector"
    }
  }]
}

The connector app cannot grant itself another site

Use an administrator or separate bootstrap principal authorized to create site permissions for the request above. Keep that higher-privilege token out of eli.ai; the stored connector credential belongs only to the Sites.Selected app.
sharepoint configjson
{
  "type": "sharepoint",
  "config": {
    "siteIds": ["acme.sharepoint.com,<site-guid>,<web-guid>"]
  },
  "credential": {
    "tenantId": "<entra-tenant-id>",
    "clientId": "<app-client-id>",
    "clientSecret": "<client-secret>"
  }
}

Slack — a customer-owned internal app

Slack requires your own internal app — this is not optional

Following Slack's 2025 tightening of rate limits for non-Marketplace apps — conversations.history and conversations.replies were cut to roughly one request per minute with a small page size for new non-Marketplace apps — a shared third-party app cannot sync a real workspace at usable speed. Each customer installs a customer-owned internal app in their own Slack workspace, which is subject to the normal (higher) tiers.
  1. Create the app from the manifest

    In api.slack.com/apps, create a new app from the provided manifest. It requests read-only bot scopes: channels:history, channels:read, groups:history, users:read (to resolve author names).
  2. Install and copy the bot token

    Install the app to the workspace and copy the Bot User OAuth Token (xoxb-…). Invite the bot to the channels you want synced — it can only read channels it is a member of.
  3. List the channel ids

    Note the channel ids to sync. The connector keeps one cursor per channel in sync_state and walks history with cursor pagination and backoff that respects the tier; Slack mrkdwn is transformed to markdown locally.
slack configjson
{
  "type": "slack",
  "config": {
    "teamId": "T0XXXXXXX",
    "channelIds": ["C0AAAAAAA", "C0BBBBBBB"]
  },
  "credential": { "botToken": "xoxb-<bot-token>" }
}

1 · Create the source

POST the config and credential. The secret is encrypted at write (AES-256-GCM, enc:v1); the response masks it and every later read returns only hasCredential.

POST/api/v1/connectorsBearer · kb:write

Create a content connector source. The credential is stored encrypted and never returned; the source starts in status active and does nothing until its first sync is enqueued.

Request body

typerequiredstring (confluence | sharepoint | slack | markdown_upload)Source type — selects the connector implementation. Fixed after creation.
configrequiredjsonNon-secret per-type config (space keys / site ids / channel ids / base urls).
credentialstringThe secret (API token / client secret / bot token). Encrypted at write, masked on every read.
Example requestbash
curl -s -X POST https://your-host/api/v1/connectors \
  -H "Authorization: Bearer $ELI_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "confluence",
    "config": { "baseUrl": "https://acme.atlassian.net/wiki", "spaceKeys": ["ENG"] },
    "credential": { "email": "svc-eli@acme.com", "apiToken": "<atlassian-api-token>" }
  }'
Responsejson
{
  "id": "01KZCONNSRC00000000000000",
  "type": "confluence",
  "status": "active",
  "config": { "baseUrl": "https://acme.atlassian.net/wiki", "spaceKeys": ["ENG"] },
  "hasCredential": true,
  "lastSyncedAt": null,
  "createdAt": "2026-07-21T09:14:03.412Z"
}

2 · Kick off the first full sync

A fullsync backfills everything. It persists every document in a page before advancing that page's checkpoint, so interruption cannot acknowledge content that was only queued. A PostgreSQL advisory lock keyed by workspace and source serializes full, incremental, prune, and permissions work even when different queues or worker replicas receive overlapping jobs.

POST/api/v1/connectors/{id}/syncBearer · kb:write

Enqueue a sync for a source. kind=full backfills from the saved checkpoint; kind=incremental polls only what changed since the last checkpoint. Returns immediately with an enqueue descriptor; the worker creates the sync_runs row after it acquires the source lock.

Request body

kindstring (full | incremental)Default full for the first run; incremental thereafter. The runner selects fullLoad vs poll accordingly.
Example requestbash
curl -s -X POST https://your-host/api/v1/connectors/01KZCONNSRC00000000000000/sync \
  -H "Authorization: Bearer $ELI_KEY" \
  -H "Content-Type: application/json" \
  -d '{"kind":"full"}'
Responsejson
{ "enqueued": true, "sourceId": "01KZCONNSRC00000000000000", "kind": "full" }

Each document is upserted by (source_id, external_id), so a re-sync updates rather than duplicates, and content-hash detection skips re-embedding anything unchanged. Every connector change, reactivation, and prune delete remains in document revision history. Watch progress in the source's sync_runs history (docs seen / added / updated / successfully tombstoned) on the Connectors page.

3 · Keep it current

After the backfill, incremental syncs do the steady-state work — Confluence by CQL watermark, SharePoint by Graph delta, Slack by per-channel cursor — and a prune pass tombstones anything deleted upstream. Trigger an incremental manually the same way ({"kind":"incremental"}) or let the schedule run it. A failed document leaves the page checkpoint unchanged; retry replays prior successful upserts idempotently. Prune first completes the authoritative remote id listing, then counts a tombstone only when its soft delete succeeds.

GET/api/v1/connectorsBearer · data:read

List the workspace's content connector sources with status, last-synced time, and masked credentials (hasCredential only).

Example requestbash
curl -s https://your-host/api/v1/connectors \
  -H "Authorization: Bearer $ELI_KEY"
Responsejson
{
  "connectors": [
    {
      "id": "01KZCONNSRC00000000000000",
      "type": "confluence",
      "status": "active",
      "hasCredential": true,
      "lastSyncedAt": "2026-07-21T09:22:41.008Z"
    }
  ]
}

Remote permissions are enforced at retrieval

The Confluence permissions job expands page read restrictions; the SharePoint permissions job expands Graph drive-item grants. Documents with explicit remote grants become acl_visibility: restricted, and retrieval admits them only when a normalized document principal intersects the authenticated actor before ranking. App user id and email principals are automatic. Provider account/group identifiers require a trusted identity/group mapping; without one those grants fail closed. Slack content remains workspace-visible unless the source is explicitly configured as restricted. Details in Content connectors.

Where to go next

The connector contract, checkpointing, and normalization: Content connectors. Cache freshness as sources change: Semantic caching. Ask questions across everything you synced: Search & ask.