Skip to content

Graph API

Base path: /api/v1/graph

The Graph module exposes the knowledge graph for the requesting user's organisation. It provides the full paginated graph, per-entry subgraphs, topic clusters derived from tag co-occurrence, aggregate statistics, and an on-demand auto-link trigger that wires together semantically similar entries via embedding cosine similarity.

See API Reference for auth, errors, and pagination.

Plan gating

Every route in this module requires Growth plan or above (min_plan="growth", feature "knowledge_graph"). Requests from trial or starter organisations are rejected with 403 PLAN_UPGRADE_REQUIRED before reaching any route handler.

Plan hierarchy: trial < starter < growth < enterprise.

Data types

GraphNode

Field Type Description
id string Node UUID.
label string Display name (entry title, username, dept name).
type "entry" \| "person" \| "department" Discriminates what the node represents.
metadata object Flexible per-type property bag.

GraphEdge

Field Type Description
source string Source node UUID.
target string Target node UUID.
type "explicit" \| "similarity" \| "same_author" \| "same_dept" Relationship kind.
weight float 1.0 for structural edges; cosine similarity score for similarity edges.

Edge types:

Type Origin
explicit Manually declared via the related_entries many-to-many table.
similarity Auto-detected: embedding cosine similarity >= 0.85.
same_author Two entries share the same created_by user.
same_dept Two entries share the same department.

GraphCluster

Field Type Description
tag string The tag that defines the cluster.
entry_ids string[] UUIDs of entries in the cluster.
size integer Count of entries in the cluster.

GraphResponse

Returned by GET /graph and GET /graph/entry/:id.

Field Type Description
nodes GraphNode[] All nodes in the graph slice.
edges GraphEdge[] All edges between the returned nodes.
clusters GraphCluster[] Topic clusters visible within this slice.
total_entries integer Total entries matching the applied filters.
page integer Current page number.
per_page integer Page size used.
has_more boolean true if additional pages exist.

GraphStatsResponse

Field Type Description
total_nodes integer Count of active entries.
total_edges integer Count of explicit links.
total_clusters integer Tag clusters containing >= 2 entries.
orphan_entries integer Entries with no edges of any kind.
avg_connections_per_entry float Mean edge count per node.
most_connected_entry_id string \| null UUID of the most-linked entry.
most_connected_entry_title string \| null Title of the most-linked entry.
most_connected_count integer Edge count for the most-linked entry.

Endpoints

GET /graph

Auth: JWT · Any role · Growth+

Returns the full knowledge graph for the organisation, paginated. Each response slice contains matching entry nodes, edges between all returned nodes, and any topic clusters visible within the slice.

Query parameters

Parameter Type Default Constraints Description
page integer 1 >= 1 Page number.
per_page integer 50 1–200 Entry nodes per page.
department_id UUID Valid UUID v4 Filter nodes to entries in this department.
tag_ids UUID[] Valid UUID v4s Entries matching any of these tags. Supports repeated params and comma-separated values.
status string Filter by entry status.
confidence string Filter by entry confidence.
include_persons boolean false true/false Add person nodes for entry authors.
include_depts boolean false true/false Add department cluster nodes.

Both formats are accepted for tag_ids:

?tag_ids=<uuid1>&tag_ids=<uuid2>
?tag_ids=<uuid1>,<uuid2>

Response — 200 OK

{
  "nodes": [
    {
      "id": "a1b2c3d4-0000-0000-0000-000000000001",
      "label": "Onboarding Best Practices",
      "type": "entry",
      "metadata": {
        "status": "published",
        "confidence": "high",
        "department_id": "dept-uuid-here",
        "tag_ids": ["tag-uuid-1", "tag-uuid-2"]
      }
    },
    {
      "id": "user-uuid-here",
      "label": "Alice Smith",
      "type": "person",
      "metadata": { "email": "alice@example.com" }
    }
  ],
  "edges": [
    {
      "source": "a1b2c3d4-0000-0000-0000-000000000001",
      "target": "a1b2c3d4-0000-0000-0000-000000000002",
      "type": "similarity",
      "weight": 0.91
    }
  ],
  "clusters": [
    {
      "tag": "onboarding",
      "entry_ids": [
        "a1b2c3d4-0000-0000-0000-000000000001",
        "a1b2c3d4-0000-0000-0000-000000000003"
      ],
      "size": 2
    }
  ],
  "total_entries": 143,
  "page": 1,
  "per_page": 50,
  "has_more": true
}

Errors

Status Code Cause
401 AUTHENTICATION_FAILED Missing or invalid JWT.
402 SUBSCRIPTION_REQUIRED Org subscription inactive or trial expired.
403 ORG_SCOPE_REQUIRED JWT does not contain an org_id claim.
403 PLAN_UPGRADE_REQUIRED Organisation is on trial or starter plan.
422 VALIDATION_ERROR A UUID query parameter contains a non-UUID value.

Notes

  • Pagination applies to entry nodes only. Edges and clusters are computed for the returned node set and are not independently paginated.
  • include_persons and include_depts expand the node count beyond per_page; total_entries and pagination fields still reflect only entry nodes.
curl -G "https://api.knora.io/api/v1/graph" \
  -H "Authorization: Bearer $TOKEN" \
  --data-urlencode "page=1" \
  --data-urlencode "per_page=50" \
  --data-urlencode "include_persons=true" \
  --data-urlencode "status=published"

GET /graph/entry/:id

Auth: JWT · Any role · Growth+

Returns the subgraph centred on a single knowledge entry — the entry itself plus its direct (depth 1) or indirect (depth 2) neighbours, along with all edges between those nodes.

Path parameters

Parameter Type Description
id UUID UUID of the knowledge entry to focus on.

Query parameters

Parameter Type Default Constraints Description
depth integer 1 1 or 2 Traversal depth from the focal entry.

depth is clamped server-side to [1, 2]; values outside this range are silently adjusted.

Response — 200 OK

{
  "nodes": [
    {
      "id": "a1b2c3d4-0000-0000-0000-000000000001",
      "label": "Onboarding Best Practices",
      "type": "entry",
      "metadata": {}
    },
    {
      "id": "a1b2c3d4-0000-0000-0000-000000000002",
      "label": "New Hire Checklist",
      "type": "entry",
      "metadata": {}
    }
  ],
  "edges": [
    {
      "source": "a1b2c3d4-0000-0000-0000-000000000001",
      "target": "a1b2c3d4-0000-0000-0000-000000000002",
      "type": "explicit",
      "weight": 1.0
    }
  ],
  "clusters": [],
  "total_entries": 2,
  "page": 1,
  "per_page": 2,
  "has_more": false
}

Errors

Status Code Cause
401 AUTHENTICATION_FAILED Missing or invalid JWT.
402 SUBSCRIPTION_REQUIRED Org subscription inactive or trial expired.
403 ORG_SCOPE_REQUIRED JWT does not contain an org_id claim.
403 PLAN_UPGRADE_REQUIRED Organisation is on trial or starter plan.
404 NOT_FOUND Entry does not exist or belongs to another org.
curl "https://api.knora.io/api/v1/graph/entry/a1b2c3d4-0000-0000-0000-000000000001?depth=2" \
  -H "Authorization: Bearer $TOKEN"

GET /graph/clusters

Auth: JWT · Any role · Growth+

Returns all topic clusters for the organisation. A cluster groups entries that share a common tag and contains at least two members. Clusters are derived from tag co-occurrence across all active entries. This endpoint is not paginated — all clusters are returned in a single response.

Response — 200 OK

Returns a JSON array of GraphCluster objects.

[
  {
    "tag": "onboarding",
    "entry_ids": [
      "a1b2c3d4-0000-0000-0000-000000000001",
      "a1b2c3d4-0000-0000-0000-000000000003",
      "a1b2c3d4-0000-0000-0000-000000000007"
    ],
    "size": 3
  },
  {
    "tag": "security",
    "entry_ids": [
      "a1b2c3d4-0000-0000-0000-000000000004",
      "a1b2c3d4-0000-0000-0000-000000000009"
    ],
    "size": 2
  }
]

An empty array is returned when no qualifying clusters exist.

Errors

Status Code Cause
401 AUTHENTICATION_FAILED Missing or invalid JWT.
402 SUBSCRIPTION_REQUIRED Org subscription inactive or trial expired.
403 ORG_SCOPE_REQUIRED JWT does not contain an org_id claim.
403 PLAN_UPGRADE_REQUIRED Organisation is on trial or starter plan.
curl "https://api.knora.io/api/v1/graph/clusters" \
  -H "Authorization: Bearer $TOKEN"

POST /graph/auto-link

Auth: JWT · admin or manager role · Growth+

Enqueues a background Celery task (graph.auto_link_entries) that scans all active entries in the organisation and creates related_entries links for pairs whose embedding cosine similarity score is >= 0.85 and that are not already explicitly linked. Returns a task ID to poll for completion.

No request body.

Response — 202 Accepted

{
  "message": "Auto-link task queued.",
  "task_id": "b3d2e1f0-aaaa-bbbb-cccc-000000000001",
  "org_id": "org-uuid-here"
}
Field Type Description
message string Human-readable confirmation.
task_id string Celery task ID. Use with GET /graph/auto-link/:task_id to poll status.
org_id string UUID of the organisation being processed.

Errors

Status Code Cause
401 AUTHENTICATION_FAILED Missing or invalid JWT.
402 SUBSCRIPTION_REQUIRED Org subscription inactive or trial expired.
403 ORG_SCOPE_REQUIRED JWT does not contain an org_id claim.
403 INSUFFICIENT_ROLE Caller's role is member — admin or manager is required.
403 PLAN_UPGRADE_REQUIRED Organisation is on trial or starter plan.

Notes

  • The task runs asynchronously. A 202 response means the task was accepted by the broker, not that linking is complete.
  • Soft time limit: 10 minutes; hard limit: 11 minutes. Large orgs may use the full limit.
  • Up to 3 automatic retries on failure, with a 60-second delay between attempts.
  • Similarity threshold: 0.85 cosine similarity. Pairs below this threshold are not linked.
  • Already-linked pairs are skipped — no duplicate links are created.
  • There is no built-in guard against concurrent tasks for the same org.
curl -X POST "https://api.knora.io/api/v1/graph/auto-link" \
  -H "Authorization: Bearer $ADMIN_TOKEN"

GET /graph/auto-link/:task_id

Auth: JWT · Any role · Growth+

Polls the status of an auto-link Celery task previously enqueued by POST /graph/auto-link.

Path parameters

Parameter Type Description
task_id string Celery task ID returned by auto-link.

Response — 200 OK

{
  "task_id": "b3d2e1f0-aaaa-bbbb-cccc-000000000001",
  "state": "SUCCESS",
  "result": { "links_created": 42 }
}
Field Type Description
task_id string The polled task ID.
state string Celery task state: PENDING, STARTED, SUCCESS, FAILURE, RETRY.
result object \| null Task result payload when state is SUCCESS; null otherwise.

Errors

Status Code Cause
401 AUTHENTICATION_FAILED Missing or invalid JWT.
402 SUBSCRIPTION_REQUIRED Org subscription inactive or trial expired.
403 ORG_SCOPE_REQUIRED JWT does not contain an org_id claim.
403 PLAN_UPGRADE_REQUIRED Organisation is on trial or starter plan.
curl "https://api.knora.io/api/v1/graph/auto-link/b3d2e1f0-aaaa-bbbb-cccc-000000000001" \
  -H "Authorization: Bearer $TOKEN"

GET /graph/stats

Auth: JWT · Any role · Growth+

Returns aggregate statistics for the organisation's knowledge graph. Useful for dashboard summary widgets, health indicators, and identifying highly connected or isolated entries.

No query parameters.

Response — 200 OK

{
  "total_nodes": 143,
  "total_edges": 312,
  "total_clusters": 18,
  "orphan_entries": 7,
  "avg_connections_per_entry": 4.36,
  "most_connected_entry_id": "a1b2c3d4-0000-0000-0000-000000000001",
  "most_connected_entry_title": "Engineering Runbook",
  "most_connected_count": 24
}

Errors

Status Code Cause
401 AUTHENTICATION_FAILED Missing or invalid JWT.
402 SUBSCRIPTION_REQUIRED Org subscription inactive or trial expired.
403 ORG_SCOPE_REQUIRED JWT does not contain an org_id claim.
403 PLAN_UPGRADE_REQUIRED Organisation is on trial or starter plan.

Notes

  • most_connected_entry_id and most_connected_entry_title are null when the org has no entries.
  • total_edges counts only explicit links; similarity and structural edges are not included in this count.
curl "https://api.knora.io/api/v1/graph/stats" \
  -H "Authorization: Bearer $TOKEN"