Skip to content

Analytics API

Base path: /api/v1/analytics

See API Reference for auth, errors, and pagination.

This module covers two sub-systems:

  • Bus Factor (/bus-factor) — knowledge-concentration risk scoring at the person and department level.
  • Staleness (/staleness) — detection and management of knowledge entries that have not been reviewed within a configurable time window.

All endpoints require Growth plan or higher. Role enforcement is performed via a live database lookup on every request — a role downgrade takes effect immediately.

Role Summary

Role Bus Factor endpoints Staleness list/stats Staleness check (POST)
member No No No
manager Yes Yes No
admin Yes Yes Yes

Bus Factor Endpoints

GET /api/v1/analytics/bus-factor/

Auth: JWT — manager or admin | Plan: Growth+

Returns a full organisation-wide risk summary: every person's score and every department's score, along with aggregate counts by risk level. Use this endpoint for dashboard overviews and periodic risk reports.

curl -X GET "https://api.knora.io/api/v1/analytics/bus-factor/" \
  -H "Authorization: Bearer $TOKEN"

Response 200 OK

{
  "person_scores": [
    {
      "user_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
      "user_name": "Alice Johnson",
      "user_email": "alice@example.com",
      "sole_entries": 14,
      "total_department_entries": 20,
      "score": 70.0,
      "risk_level": "critical",
      "department_id": "d1e2f3a4-b5c6-7890-abcd-ef1234567890",
      "department_name": "Engineering"
    }
  ],
  "department_scores": [
    {
      "department_id": "d1e2f3a4-b5c6-7890-abcd-ef1234567890",
      "department_name": "Engineering",
      "total_entries": 20,
      "concentration_index": 0.72,
      "risk_level": "critical",
      "top_holders": [
        {
          "user_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
          "user_name": "Alice Johnson",
          "sole_entries": 14,
          "percentage": 70.0,
          "risk_level": "critical"
        }
      ]
    }
  ],
  "critical_count": 3,
  "high_count": 5,
  "medium_count": 8,
  "low_count": 12,
  "overall_risk_level": "critical"
}

Top-level fields

Field Type Description
person_scores PersonRiskScore[] One entry per user in the org.
department_scores DepartmentRiskScore[] One entry per department.
critical_count int Number of persons at critical risk.
high_count int Number of persons at high risk.
medium_count int Number of persons at medium risk.
low_count int Number of persons at low risk.
overall_risk_level string critical | high | medium | low

PersonRiskScore fields

Field Type Description
user_id UUID Unique user identifier.
user_name string Full display name.
user_email string Email address.
sole_entries int Knowledge entries where this person is the only author/owner.
total_department_entries int Total entries in their department.
score float Risk score 0–100 (percentage of dept entries solely held).
risk_level string critical | high | medium | low
department_id UUID | null Department, if any.
department_name string | null Department name, if any.

DepartmentRiskScore fields

Field Type Description
department_id UUID Unique department identifier.
department_name string Department display name.
total_entries int Total active knowledge entries in the department.
concentration_index float Herfindahl-style index 0–1; higher = more concentrated knowledge.
risk_level string critical | high | medium | low
top_holders DepartmentConcentration[] Top knowledge holders within the department.

DepartmentConcentration fields

Field Type Description
user_id UUID User identifier.
user_name string Full display name.
sole_entries int Entries solely held by this person.
percentage float Percentage of department entries held solely (0–100).
risk_level string critical | high | medium | low

GET /api/v1/analytics/bus-factor/person/{user_id}

Auth: JWT — manager or admin | Plan: Growth+

Returns the bus factor risk score for a single user within the caller's organisation.

Parameter In Type Description
user_id path UUID Target user.
curl -X GET "https://api.knora.io/api/v1/analytics/bus-factor/person/a1b2c3d4-e5f6-7890-abcd-ef1234567890" \
  -H "Authorization: Bearer $TOKEN"

Response 200 OKPersonRiskScore object (see field descriptions above).

{
  "user_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "user_name": "Alice Johnson",
  "user_email": "alice@example.com",
  "sole_entries": 14,
  "total_department_entries": 20,
  "score": 70.0,
  "risk_level": "critical",
  "department_id": "d1e2f3a4-b5c6-7890-abcd-ef1234567890",
  "department_name": "Engineering"
}

GET /api/v1/analytics/bus-factor/department/{dept_id}

Auth: JWT — manager or admin | Plan: Growth+

Returns the bus factor risk breakdown for a single department within the caller's organisation.

Parameter In Type Description
dept_id path UUID Target department.
curl -X GET "https://api.knora.io/api/v1/analytics/bus-factor/department/d1e2f3a4-b5c6-7890-abcd-ef1234567890" \
  -H "Authorization: Bearer $TOKEN"

Response 200 OKDepartmentRiskScore object (see field descriptions above).

{
  "department_id": "d1e2f3a4-b5c6-7890-abcd-ef1234567890",
  "department_name": "Engineering",
  "total_entries": 20,
  "concentration_index": 0.72,
  "risk_level": "critical",
  "top_holders": [
    {
      "user_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
      "user_name": "Alice Johnson",
      "sole_entries": 14,
      "percentage": 70.0,
      "risk_level": "critical"
    }
  ]
}

GET /api/v1/analytics/bus-factor/critical

Auth: JWT — manager or admin | Plan: Growth+

Returns only people and departments currently at critical risk level. Intended for dashboard alert widgets.

curl -X GET "https://api.knora.io/api/v1/analytics/bus-factor/critical" \
  -H "Authorization: Bearer $TOKEN"

Response 200 OK

{
  "critical_people": [
    {
      "user_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
      "user_name": "Alice Johnson",
      "user_email": "alice@example.com",
      "sole_entries": 14,
      "total_department_entries": 20,
      "score": 70.0,
      "risk_level": "critical",
      "department_id": "d1e2f3a4-b5c6-7890-abcd-ef1234567890",
      "department_name": "Engineering"
    }
  ],
  "critical_departments": [
    {
      "department_id": "d1e2f3a4-b5c6-7890-abcd-ef1234567890",
      "department_name": "Engineering",
      "total_entries": 20,
      "concentration_index": 0.72,
      "risk_level": "critical",
      "top_holders": []
    }
  ],
  "total_critical": 2
}
Field Type Description
critical_people PersonRiskScore[] Users at critical risk level.
critical_departments DepartmentRiskScore[] Departments at critical risk level.
total_critical int Total number of critical-risk items (people + departments).

Staleness Endpoints

GET /api/v1/analytics/staleness/

Auth: JWT — manager or admin | Plan: Growth+

Lists all active knowledge entries not reviewed within a configurable number of days.

Parameter In Type Default Description
days query int 30 Staleness threshold in days. Values less than 1 are silently reset to 30.
# Default 30-day threshold
curl -X GET "https://api.knora.io/api/v1/analytics/staleness/" \
  -H "Authorization: Bearer $TOKEN"

# Custom 60-day threshold
curl -X GET "https://api.knora.io/api/v1/analytics/staleness/?days=60" \
  -H "Authorization: Bearer $TOKEN"

Response 200 OK

{
  "entries": [
    {
      "entry_id": "e1f2a3b4-c5d6-7890-abcd-ef1234567890",
      "title": "Deployment Runbook v3",
      "department_id": "d1e2f3a4-b5c6-7890-abcd-ef1234567890",
      "department_name": "Engineering",
      "created_by": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
      "creator_name": "Alice Johnson",
      "status": "active",
      "last_reviewed_at": "2026-01-10T09:00:00Z",
      "days_since_review": 141,
      "created_at": "2025-06-15T12:00:00Z"
    }
  ],
  "total": 1,
  "days_threshold": 30
}
Field Type Description
entries StaleEntry[] List of stale entries.
total int Total number of stale entries returned.
days_threshold int The threshold used for this response.

StaleEntry fields

Field Type Description
entry_id UUID Knowledge entry identifier.
title string Entry title.
department_id UUID | null Department the entry belongs to.
department_name string | null Department name.
created_by UUID User ID of the original creator.
creator_name string | null Display name of the creator.
status string Current entry status (e.g. active, needs_review).
last_reviewed_at datetime | null ISO 8601 timestamp of the last review; null if never reviewed.
days_since_review int Days elapsed since last review (or since creation if never reviewed).
created_at datetime ISO 8601 timestamp when the entry was created.

GET /api/v1/analytics/staleness/stats

Auth: JWT — manager or admin | Plan: Growth+

Returns aggregated staleness statistics: entry counts broken down by status, average days since review, and the stale percentage.

Parameter In Type Default Description
days query int 30 Threshold for computing stale_count. Values less than 1 are silently reset to 30.
curl -X GET "https://api.knora.io/api/v1/analytics/staleness/stats?days=45" \
  -H "Authorization: Bearer $TOKEN"

Response 200 OK

{
  "total_active": 84,
  "needs_review_count": 12,
  "active_count": 68,
  "archived_count": 4,
  "stale_count": 17,
  "average_days_since_review": 22.4,
  "days_threshold": 30,
  "stale_percentage": 20.24
}
Field Type Description
total_active int Total number of non-archived entries in the org.
needs_review_count int Entries currently flagged as needs_review.
active_count int Entries with status active.
archived_count int Entries with status archived.
stale_count int Entries not reviewed within the configured threshold.
average_days_since_review float Mean days since last review across all active entries.
days_threshold int The threshold used for this response.
stale_percentage float stale_count / total_active * 100, clamped 0–100.

POST /api/v1/analytics/staleness/check

Auth: JWT — admin only | Plan: Growth+

Manually triggers a staleness check. Every entry not reviewed within the threshold is flagged with status needs_review. Safe to call multiple times — entries already flagged are not double-counted.

Request body (optional JSON)

Field Type Required Description
days int No Staleness threshold in days. Defaults to 30 if omitted, non-integer, or less than 1.
curl -X POST "https://api.knora.io/api/v1/analytics/staleness/check" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"days": 45}'

Response 200 OK

{
  "checked": 84,
  "flagged": 17,
  "days_threshold": 30,
  "message": "Staleness check complete. 17 entries flagged for review."
}
Field Type Description
checked int Total number of entries evaluated.
flagged int Number of entries newly flagged as needs_review.
days_threshold int The threshold used for this run.
message string Human-readable summary of the operation.

Error Reference

HTTP 401 — Authentication errors

Code Cause
INVALID_TOKEN JWT is malformed, the sub claim is not a valid UUID, or the org_id claim is not a valid UUID.
USER_NOT_FOUND The user referenced by the JWT does not exist in the database or the account has been deactivated.

HTTP 402 — Subscription errors

Code Cause
SUBSCRIPTION_REQUIRED The organisation's account is inactive or the trial period has expired.

HTTP 403 — Authorization errors

Code Cause
INSUFFICIENT_ROLE The authenticated user's role does not meet the minimum required for the endpoint.
ORG_SCOPE_REQUIRED The JWT does not contain an org_id claim.
PLAN_UPGRADE_REQUIRED The organisation's plan is below Growth. The error body includes required_plan and feature ("analytics") in the details.

HTTP 404 — Not found

Situation Description
Unknown user_id The user does not exist or does not belong to the caller's organisation.
Unknown dept_id The department does not exist or does not belong to the caller's organisation.

Notes

Org scoping — All data is automatically scoped to the caller's organisation via the org_id JWT claim. There is no way to query another organisation's data through these endpoints.

Live role enforcement — Role checks bypass the JWT role claim and hit the database on every request. A role downgrade takes effect immediately without waiting for the token to expire.

Staleness threshold — The days parameter must be a positive integer. Any value less than 1 is silently coerced to the default of 30. There is no upper bound enforced by the API.

No pagination — List endpoints (GET /staleness/) return all matching entries in a single response. For very large organisations, use the days threshold to narrow the result set.