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
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
- Decode and verify the state token →
org_id,connector_type. - Exchange the authorization code for tokens.
- 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.
- Return an HTML page that posts a
window.postMessageto 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
- Only
webhook-type integrations are accepted — all other types are rejected. - The
X-Knora-Signatureheader must containsha256=<hex>computed by HMAC-SHA256 over the raw request body using the integration's signing secret. org_idis 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