Dashboard API¶
Base path: /api/v1/dashboard
The Dashboard module provides management-facing analytics for a single organisation. All data is strictly scoped to the caller's organisation — org_id is read from the JWT and cannot be overridden by request parameters.
See API Reference for auth, errors, and pagination.
Access: All endpoints require role manager or admin and plan growth or higher.
GET /dashboard/overview¶
Returns the main dashboard summary: total entries, total users, health score, and query counts.
Auth: JWT (manager+) · Plan: Growth+
Cache: Results are pre-warmed by the Celery task output.dashboard.warm_dashboard_cache and served from Redis under dashboard:overview:<org_id> with a 10-minute TTL. On a cache miss or Redis failure the endpoint falls back to live database queries transparently (logged at WARNING). The other endpoints always hit the database directly.
Response 200 OK
{
"total_entries": 412,
"total_users": 28,
"health_score": {
"coverage": 83.33,
"freshness": 61.40,
"verification": 78.88,
"gaps": 91.20,
"overall": 78.32
},
"total_queries": 1045,
"unanswered_queries": 92
}
| Field | Type | Description |
|---|---|---|
total_entries |
integer | Total non-archived knowledge entries |
total_users |
integer | Total active user accounts |
health_score |
object | Knowledge health breakdown — see HealthScore schema |
total_queries |
integer | Cumulative queries submitted by all users |
unanswered_queries |
integer | Queries that received no matching entry answer |
GET /dashboard/health¶
Returns the standalone HealthScore object for the organisation. Useful for polling health independently without fetching the full overview payload. Returns the same object embedded in /dashboard/overview.
Auth: JWT (manager+) · Plan: Growth+
Response 200 OK
See HealthScore schema for field definitions.
GET /dashboard/activity¶
Returns a time-sorted feed of recent events: entry creations, verifications, queries, and flags. The feed covers the last 7 days regardless of limit. Events are fetched up to limit // 4 (minimum 5) per event type, then merged, sorted descending by timestamp, and capped at limit.
Auth: JWT (manager+) · Plan: Growth+
Query parameters
| Parameter | Type | Default | Max | Description |
|---|---|---|---|---|
limit |
integer | 20 |
100 |
Maximum number of activity events to return |
curl -s \
-H "Authorization: Bearer $TOKEN" \
"https://api.knora.io/api/v1/dashboard/activity?limit=50" | jq .
Response 200 OK
{
"activities": [
{
"type": "entry_created",
"entry_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"entry_title": "Onboarding Checklist",
"user_id": "a1b2c3d4-0000-0000-0000-000000000001",
"user_name": "Alice Smith",
"timestamp": "2026-05-31T14:22:00Z",
"details": null
},
{
"type": "query_made",
"entry_id": null,
"entry_title": null,
"user_id": "a1b2c3d4-0000-0000-0000-000000000002",
"user_name": "Bob Jones",
"timestamp": "2026-05-31T13:55:10Z",
"details": "How do I submit an expense report?"
}
],
"total": 2
}
activities[].type values
| Value | Meaning |
|---|---|
entry_created |
A new knowledge entry was created |
entry_verified |
An entry was marked as verified |
query_made |
A user submitted a question; details contains the first 100 characters of the query text |
entry_flagged |
An entry was flagged for review |
Activity item fields
| Field | Type | Nullable | Description |
|---|---|---|---|
type |
string | No | Event type (see table above) |
entry_id |
UUID | Yes | Relevant entry ID (null for query_made) |
entry_title |
string | Yes | Entry title (null for query_made) |
user_id |
UUID | Yes | User who triggered the event |
user_name |
string | Yes | Display name of the user |
timestamp |
ISO 8601 datetime | No | When the event occurred |
details |
string | Yes | Extra context; populated only for query_made |
GET /dashboard/departments¶
Returns per-department statistics: entry counts, coverage percentage, bus-factor risk level, and verification status. Departments are sorted by entry_count descending. If bus-factor computation fails, risk_level defaults to "low" for all departments and a warning is logged.
Auth: JWT (manager+) · Plan: Growth+
curl -s \
-H "Authorization: Bearer $TOKEN" \
https://api.knora.io/api/v1/dashboard/departments | jq .
Response 200 OK
{
"departments": [
{
"department_id": "d1000000-0000-0000-0000-000000000001",
"department_name": "Engineering",
"entry_count": 87,
"coverage": 100.0,
"risk_level": "low",
"verified_count": 72,
"needs_review_count": 5
},
{
"department_id": "d2000000-0000-0000-0000-000000000002",
"department_name": "Finance",
"entry_count": 4,
"coverage": 40.0,
"risk_level": "critical",
"verified_count": 1,
"needs_review_count": 3
}
],
"total": 2
}
Department stat fields
| Field | Type | Description |
|---|---|---|
department_id |
UUID | Unique identifier of the department |
department_name |
string | Human-readable department name |
entry_count |
integer | Total non-archived knowledge entries |
coverage |
float 0–100 | Entry coverage percentage. 100% when entry_count >= 10; scales linearly below that threshold |
risk_level |
string | Bus-factor concentration risk: critical / high / medium / low |
verified_count |
integer | Entries with verified status |
needs_review_count |
integer | Entries flagged as needing review |
GET /dashboard/trending¶
Returns the most frequently asked questions in the organisation over a rolling look-back window. Always capped at the top 10 results.
Auth: JWT (manager+) · Plan: Growth+
Query parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
days |
integer | 7 |
Look-back window in days |
curl -s \
-H "Authorization: Bearer $TOKEN" \
"https://api.knora.io/api/v1/dashboard/trending?days=30" | jq .
Response 200 OK
{
"queries": [
{
"query_text": "How do I request parental leave?",
"count": 34,
"has_answer": true,
"last_asked_at": "2026-05-31T16:45:00Z"
},
{
"query_text": "How do I get access to the production environment?",
"count": 18,
"has_answer": false,
"last_asked_at": "2026-05-31T11:20:00Z"
}
],
"days": 7,
"total": 2
}
Trending query fields
| Field | Type | Nullable | Description |
|---|---|---|---|
query_text |
string | No | The exact query string submitted |
count |
integer | No | Number of times asked in the window |
has_answer |
boolean | No | Whether at least one matching knowledge entry exists |
last_asked_at |
ISO 8601 datetime | Yes | Timestamp of the most recent occurrence |
Queries with has_answer: false represent knowledge gaps and can be used to prioritise new entry creation.
HealthScore¶
Returned inside OverviewResponse.health_score and as the standalone response from GET /dashboard/health.
All sub-scores are floats in the range 0.0 – 100.0 (two decimal places).
| Field | Weight | Calculation |
|---|---|---|
coverage |
25% | Percentage of departments that have at least 10 entries |
freshness |
25% | Percentage of entries reviewed in the last 30 days |
verification |
30% | Percentage of entries with verified status |
gaps |
20% | answered_queries / total_queries * 100; defaults to 100 when no queries exist |
overall |
— | Weighted average: coverage×0.25 + freshness×0.25 + verification×0.30 + gaps×0.20 |