Skip to content

Integrations API

Base path: /api/v1/integrations

Manages external source integrations and custom webhook integrations. Integrations pull data from third-party systems (Slack, GitHub, Notion, Google Drive, Gmail, etc.) into the Knora knowledge base via direct connector sync, or accept inbound JSON payloads via a signed webhook URL. Multiple integrations of the same connector type are supported per organisation.

See API Reference for auth, errors, and pagination.

Role requirements for this module:

Endpoint Minimum role
GET /connectors manager
GET / manager
POST / admin
DELETE /\<id> admin
GET /\<id>/health manager
GET /\<id>/connection-health manager
POST /\<id>/sync admin
GET /\<id>/history manager
GET /\<id>/preview manager
GET /oauth/start admin
GET /oauth/callback none (OAuth redirect)
POST /webhook/\<id>/receive none (HMAC-signed)

Plan gating: POST / is blocked when the org has reached its plan's total integration limit. When the limit is hit, the server returns 403 PLAN_UPGRADE_REQUIRED (role check runs first — non-admins receive 403 INSUFFICIENT_ROLE before the limit is evaluated).

Error codes specific to this module:

HTTP Code Cause
403 PLAN_UPGRADE_REQUIRED Org is at its plan's integration cap
403 FORBIDDEN Webhook HMAC/secret verification failed
403 ORG_NOT_FOUND JWT references an organisation that no longer exists

GET /connectors

Returns the full connector registry including per-connector configuration schemas. The frontend uses this to render a typed setup form for each integration type.

Auth: JWT required. Minimum role: manager.

Response — 200 OK

{
  "connectors": [
    {
      "type": "slack",
      "display_name": "Slack",
      "description": "Import messages and threads from Slack channels.",
      "icon": "slack",
      "data_description": "Channel messages, threads, and replies",
      "auth_type": "oauth2",
      "auth_flow": "oauth_popup",
      "provider": "slack",
      "config_schema": [
        {
          "key": "workspace_token",
          "label": "Bot Token",
          "type": "password",
          "required": true,
          "placeholder": "xoxb-...",
          "help_text": "A Slack bot token with channels:history scope.",
          "default": null,
          "options": []
        }
      ]
    }
  ],
  "total": 1
}

Connector types: slack, google_drive, gmail, notion, github, webhook

Config field types: text, password, select, boolean, number

auth_flow values: api_key (credentials entered manually), oauth_popup (browser OAuth popup via /oauth/start)

provider: OAuth provider identifier (e.g. "google", "slack", "github", "notion"). null for non-OAuth connectors.

curl example

curl -s \
  -H "Authorization: Bearer $TOKEN" \
  https://api.knora.io/api/v1/integrations/connectors | jq

GET /

List all integrations belonging to the caller's organisation. An organisation may have multiple integrations of the same connector type (e.g. two Slack workspaces). Raw connector config is never returned for security.

Auth: JWT required. Minimum role: manager.

Response — 200 OK

{
  "integrations": [
    {
      "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
      "org_id": "11111111-2222-3333-4444-555555555555",
      "type": "slack",
      "name": "Engineering Slack",
      "status": "active",
      "last_sync_at": "2026-05-30T14:22:00Z",
      "created_by": "aaaabbbb-cccc-dddd-eeee-ffffffffffff",
      "created_at": "2026-04-01T09:00:00Z",
      "updated_at": "2026-05-30T14:22:05Z"
    }
  ],
  "total": 1
}

Integration status values: active, inactive, error, syncing

curl example

curl -s \
  -H "Authorization: Bearer $TOKEN" \
  https://api.knora.io/api/v1/integrations/ | jq

POST /

Create a new integration. Multiple integrations of the same connector type are allowed per organisation. Blocked when the organisation is at its plan's total integration limit.

Auth: JWT required. Minimum role: admin.

Body

Field Type Required Description
type string yes Connector type — one of slack, google_drive, gmail, notion, github, webhook
name string yes Human-readable label. 1–255 characters.
config object no Connector-specific fields. See GET /connectors for required keys per type. Defaults to {}.
pending_integration_id string no UUID of a pending integration created by the OAuth callback. When set, finalizes that record instead of creating a new one and does not count against the plan limit.
{
  "type": "github",
  "name": "Backend Repo",
  "config": {
    "access_token": "ghp_...",
    "repositories": ["myorg/backend"]
  }
}

Response — 201 Created

{
  "id": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
  "org_id": "11111111-2222-3333-4444-555555555555",
  "type": "github",
  "name": "Backend Repo",
  "status": "active",
  "last_sync_at": null,
  "created_by": "aaaabbbb-cccc-dddd-eeee-ffffffffffff",
  "created_at": "2026-06-01T10:00:00Z",
  "updated_at": "2026-06-01T10:00:00Z"
}

Errors

HTTP Code Cause
400 VALIDATION_ERROR Missing or invalid body fields
401 UNAUTHORIZED No JWT
402 SUBSCRIPTION_REQUIRED Trial expired or subscription inactive
403 INSUFFICIENT_ROLE Caller is member or manager
403 PLAN_UPGRADE_REQUIRED Org is at its integration cap

curl example

curl -s -X POST \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "slack",
    "name": "Engineering Slack",
    "config": { "workspace_token": "xoxb-..." }
  }' \
  https://api.knora.io/api/v1/integrations/ | jq

DELETE /\<integration_id>

Remove an integration. Irreversible — all sync history is cascade-deleted.

Auth: JWT required. Minimum role: admin.

Path parameters

Param Type Description
integration_id UUID ID of the integration to delete

Response — 204 No Content (empty body)

Errors

HTTP Code Cause
401 UNAUTHORIZED No JWT
403 INSUFFICIENT_ROLE Caller is member or manager
404 (service-level) Integration not found or does not belong to this org

curl example

curl -s -X DELETE \
  -H "Authorization: Bearer $TOKEN" \
  https://api.knora.io/api/v1/integrations/b2c3d4e5-f6a7-8901-bcde-f12345678901

GET /\<integration_id>/health

Check the structural health of an integration's connection. Returns the integration's status from the database. For a live credential probe against the remote API, use /connection-health.

Auth: JWT required. Minimum role: manager.

Path parameters

Param Type Description
integration_id UUID ID of the integration to check

Response — 200 OK

{
  "integration_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "status": "active",
  "healthy": true,
  "message": "Connection is active.",
  "checked_at": "2026-06-01T10:05:00Z"
}

healthy is true when the connection is confirmed active, false when missing, broken, or in an error state.

Errors

HTTP Code Cause
401 UNAUTHORIZED No JWT
403 INSUFFICIENT_ROLE Caller is a member
404 (service-level) Integration not found or does not belong to this org

curl example

curl -s \
  -H "Authorization: Bearer $TOKEN" \
  https://api.knora.io/api/v1/integrations/a1b2c3d4-e5f6-7890-abcd-ef1234567890/health | jq

GET /\<integration_id>/connection-health

Run a live connectivity probe against the remote API for this integration. Actually calls the connector's API (e.g. Slack auth.test, GitHub GET /user, Notion GET /users/me) to verify that the stored credentials are still valid. Webhook integrations always return healthy: true.

Auth: JWT required. Minimum role: manager.

Path parameters

Param Type Description
integration_id UUID ID of the integration to probe

Response — 200 OK (same shape as /health)

{
  "integration_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "status": "active",
  "healthy": true,
  "message": "Slack credentials verified.",
  "checked_at": "2026-06-01T10:06:00Z"
}

Example — unhealthy response

{
  "integration_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "status": "error",
  "healthy": false,
  "message": "GitHub token returned 401 Unauthorized. The token may have been revoked.",
  "checked_at": "2026-06-01T10:06:10Z"
}

This call makes an outbound network request to the source system. Avoid polling it in a tight loop.

Errors

HTTP Code Cause
401 UNAUTHORIZED No JWT
403 INSUFFICIENT_ROLE Caller is a member
404 (service-level) Integration not found or does not belong to this org

curl example

curl -s \
  -H "Authorization: Bearer $TOKEN" \
  https://api.knora.io/api/v1/integrations/a1b2c3d4-e5f6-7890-abcd-ef1234567890/connection-health | jq

POST /\<integration_id>/sync

Manually trigger a sync for the integration. Returns 202 Accepted immediately; the sync runs asynchronously. Poll GET /\<id>/history for the result.

Auth: JWT required. Minimum role: admin.

Path parameters

Param Type Description
integration_id UUID ID of the integration to sync

Response — 202 Accepted

{
  "sync_job_id": "ccccdddd-eeee-ffff-0000-111122223333",
  "integration_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "status": "running",
  "message": "Sync triggered successfully."
}

Errors

HTTP Code Cause
401 UNAUTHORIZED No JWT
403 INSUFFICIENT_ROLE Caller is member or manager
404 (service-level) Integration not found or does not belong to this org

curl example

curl -s -X POST \
  -H "Authorization: Bearer $TOKEN" \
  https://api.knora.io/api/v1/integrations/a1b2c3d4-e5f6-7890-abcd-ef1234567890/sync | jq

GET /\<integration_id>/history

Return the sync job history for an integration, ordered most recent first. No pagination — all records are returned.

Auth: JWT required. Minimum role: manager.

Path parameters

Param Type Description
integration_id UUID ID of the integration

Response — 200 OK

{
  "integration_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "jobs": [
    {
      "id": "ccccdddd-eeee-ffff-0000-111122223333",
      "integration_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
      "status": "completed",
      "started_at": "2026-05-30T14:00:00Z",
      "completed_at": "2026-05-30T14:22:00Z",
      "records_synced": 1500,
      "records_found": 1520,
      "records_imported": 1500,
      "records_skipped": 20,
      "threads_grouped": 340,
      "entries_created": 980,
      "error_message": null,
      "created_at": "2026-05-30T14:00:00Z",
      "updated_at": "2026-05-30T14:22:05Z"
    }
  ],
  "total": 1
}

Sync job status values: pending, running, completed, failed

Counter fields:

Field Description
records_synced Raw records fetched from the source API
records_found Total records discovered by the ingestion task
records_imported Records successfully processed
records_skipped Records filtered out (duplicates, empty content, etc.)
threads_grouped Message threads assembled from individual records
entries_created KnowledgeEntry rows written to the database

All counter fields are null until the Celery ingestion task completes.

Errors

HTTP Code Cause
401 UNAUTHORIZED No JWT
403 INSUFFICIENT_ROLE Caller is a member
404 (service-level) Integration not found or does not belong to this org

curl example

curl -s \
  -H "Authorization: Bearer $TOKEN" \
  https://api.knora.io/api/v1/integrations/a1b2c3d4-e5f6-7890-abcd-ef1234567890/history | jq

GET /\<integration_id>/preview

Estimate the volume of data that would be imported in the next sync. Makes lightweight read-only API calls against the source system without writing anything.

Auth: JWT required. Minimum role: manager.

Path parameters

Param Type Description
integration_id UUID ID of the integration to preview

Response — 200 OK

{
  "integration_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "connector_type": "github",
  "summary": "Found 3 repos, 245 issues, 89 PRs",
  "items": [
    { "label": "repositories", "count": 3 },
    { "label": "issues", "count": 245 },
    { "label": "pull requests", "count": 89 }
  ],
  "estimated_entries": 334,
  "previewed_at": "2026-06-01T10:10:00Z"
}
Field Type Description
summary string One-line human-readable summary
items array Per-resource-type count. count may be null when the connector cannot estimate.
estimated_entries integer or null Rough total KnowledgeEntry rows that would be created. null when the connector cannot estimate.

Counts are best-effort. Webhook integrations return an empty items list. This call makes outbound read-only requests to the source API.

Errors

HTTP Code Cause
401 UNAUTHORIZED No JWT
403 INSUFFICIENT_ROLE Caller is a member
404 (service-level) Integration not found or does not belong to this org

curl example

curl -s \
  -H "Authorization: Bearer $TOKEN" \
  https://api.knora.io/api/v1/integrations/a1b2c3d4-e5f6-7890-abcd-ef1234567890/preview | jq

GET /oauth/start

Start an OAuth popup flow for a connector that uses auth_flow: "oauth_popup". Returns the provider authorization URL so the frontend can open it in a popup window. The encrypted state token encodes org_id + connector_type to prevent CSRF and route the callback back to the correct record.

Auth: JWT required. Minimum role: admin. Blocked by integration limit (same as POST /).

Query parameters

Param Required Description
connector yes Connector type — one of gmail, google_drive, slack, notion, github
org_id yes UUID of the organisation initiating the connection
integration_id no UUID of an existing pending integration to re-authorize

Response — 200 OK

{
  "auth_url": "https://accounts.google.com/o/oauth2/v2/auth?client_id=...&state=...",
  "connector": "gmail"
}

Open auth_url in a popup. The popup will post a window.postMessage event back to the opener when the flow completes (see /oauth/callback).

Errors

HTTP Code Cause
400 MISSING_PARAM connector or org_id query parameter is absent
400 INVALID_PARAM org_id is not a valid UUID
400 UNKNOWN_CONNECTOR Connector type is not registered
400 OAUTH_NOT_SUPPORTED Connector does not use oauth_popup flow
401 UNAUTHORIZED No JWT
403 INSUFFICIENT_ROLE Caller is not an admin
500 OAUTH_CONFIG_ERROR Server-side OAuth credentials are not configured

curl example

curl -s \
  -H "Authorization: Bearer $TOKEN" \
  "https://api.knora.io/api/v1/integrations/oauth/start?connector=gmail&org_id=11111111-2222-3333-4444-555555555555" | jq

GET /oauth/callback

OAuth 2.0 callback endpoint. The provider redirects here after the user grants (or denies) access. No JWT required — security comes from the encrypted state token.

Flow

  1. Decode and verify the state token → org_id, connector_type.
  2. Exchange the authorization code for tokens.
  3. Encrypt tokens and store them on the Integration record. If no Integration record exists for this org+connector yet, create one in a pending state.
  4. Return an HTML page that posts a window.postMessage to the opener and closes the popup.

Query parameters (set by provider redirect, not called directly)

Param Description
code Authorization code from provider
state Encrypted state token from /oauth/start
error Set by provider on denial; triggers an error message

Response — 200 OK (HTML)

Returns an HTML page that executes one of these postMessage events:

// Success
{ type: 'knora:oauth:success', connector: 'gmail', account: 'user@example.com', integrationId: '<uuid>' }

// Failure
{ type: 'knora:oauth:error', connector: 'gmail', error: 'Access denied by user.' }

The popup closes itself after posting the message. The frontend should listen for these events on window to detect the result and trigger POST / with pending_integration_id to finalize the integration.

POST /webhook/\<integration_id>/receive

Inbound endpoint for custom webhook integrations. External systems POST their JSON payload here. No JWT required — authenticated by HMAC signature.

Security model

  1. Only webhook-type integrations are accepted — all other types are rejected.
  2. The X-Knora-Signature header must contain sha256=<hex> computed by HMAC-SHA256 over the raw request body using the integration's signing secret.
  3. org_id is resolved from the integration record itself — a leaked URL cannot be used against a different organisation's data.

Path parameters

Param Type Description
integration_id UUID ID of the webhook integration

Headers

Header Required Description
X-Knora-Signature yes sha256=<hex> HMAC-SHA256 of the raw request body
Content-Type yes application/json

Body

Any JSON object. Must contain at least one of text, body, or content with non-empty string content for the transformer to produce a usable KnowledgeEntry.

{
  "text": "Deployment pipeline completed successfully for release v2.4.1.",
  "source": "ci-system",
  "timestamp": "2026-06-01T10:15:00Z"
}

Response — 202 Accepted (payload accepted, entry created)

{
  "integration_id": "ddddeeee-ffff-0000-1111-222233334444",
  "accepted": true,
  "entry_id": "55556666-7777-8888-9999-aaaabbbbcccc",
  "message": "Payload accepted."
}

Response — 200 OK (payload received but not accepted, e.g. empty content)

{
  "integration_id": "ddddeeee-ffff-0000-1111-222233334444",
  "accepted": false,
  "entry_id": null,
  "message": "Payload did not contain processable content."
}

Errors

HTTP Code Cause
400 VALIDATION_ERROR Signature header format is invalid
403 FORBIDDEN HMAC signature does not match
404 (service-level) integration_id not found or is not a webhook type

Generating the signature (Python)

import hmac, hashlib

secret = b"your-integration-signing-secret"
body = b'{"text": "Hello from my system"}'
signature = "sha256=" + hmac.new(secret, body, hashlib.sha256).hexdigest()
# Set X-Knora-Signature: <signature>

curl example

BODY='{"text":"Deployment completed for v2.4.1"}'
SECRET="your-signing-secret"
SIG="sha256=$(printf '%s' "$BODY" | openssl dgst -sha256 -hmac "$SECRET" | awk '{print $2}')"

curl -s -X POST \
  -H "Content-Type: application/json" \
  -H "X-Knora-Signature: $SIG" \
  -d "$BODY" \
  https://api.knora.io/api/v1/integrations/webhook/ddddeeee-ffff-0000-1111-222233334444/receive | jq