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.
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 OK — PersonRiskScore 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 OK — DepartmentRiskScore 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.