Level 4: Insights API
Access coaching insights generated from student learning activity to power dashboards, reports, and intervention workflows.
What Insights Provides
The Insights API gives you read access to coaching insights that TimeBack generates from Caliper events (Level 3). By integrating with Insights, you enable:
- Student behavior dashboards showing time-on-task and waste patterns
- Session-level analytics with detailed insight breakdowns
- Trend analysis over configurable time ranges
- Cross-session aggregations for progress tracking
Without the Insights API, you can send learning events but can't programmatically access the insights TimeBack generates. With Insights, you can build custom dashboards and integrate coaching data into your app's existing analytics.
Prerequisites
Before implementing Insights:
- Implement Level 3 (Caliper Events)—insights are generated from Caliper events you send
- Request OAuth credentials with the
events.readonlyscope (see Authentication Guide)
API Endpoints
The Insights API provides four endpoints for accessing coaching data:
| Endpoint | Description |
|---|---|
GET /insights/1.0/users/{userId} |
Get insights for a specific user |
GET /insights/1.0/users/{userId}/sessions |
Get session list for a user |
GET /insights/1.0/sessions/{sessionId} |
Get insights for a specific session |
GET /insights/1.0/users/{userId}/overview |
Get aggregated trend and breakdown data |
Authentication
All Insights endpoints require OAuth 2.0 authentication. Request the events read scope:
scope: https://purl.imsglobal.org/spec/caliper/v1p2/scope/events.readonly
Include your access token in requests:
const response = await fetch(`https://platform.timeback.com/insights/1.0/users/${userId}`, {
method: 'GET',
headers: {
Authorization: `Bearer ${accessToken}`,
'Content-Type': 'application/json',
},
});
Get User Insights
Retrieve insights for a specific user with optional filtering and pagination.
GET /insights/1.0/users/{userId}
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
applicationId |
uuid | No | Filter insights to a specific learning app |
after |
datetime | No | Filter insights after this timestamp (inclusive) |
before |
datetime | No | Filter insights before this timestamp (inclusive) |
limit |
integer | No | Maximum items to return (1-100, default 20) |
offset |
integer | No | Number of items to skip (default 0) |
type |
string | No | Comma-separated insight type slugs to filter by. Overrides category and default. |
category |
string | No | Comma-separated categories (e.g., Engagement,Proctoring,Cheating). Overrides default. |
Example Request
const userId = 'student-platform-id'; // From LTI 'sub' or roster lookup
const response = await fetch(
`https://platform.timeback.com/insights/1.0/users/${userId}?limit=10&after=2025-01-01T00:00:00Z`,
{
headers: { Authorization: `Bearer ${accessToken}` },
},
);
const data = await response.json();
Example Response
{
"session": {
"startedAtTime": "2025-01-15T09:00:00Z",
"endedAtTime": "2025-01-15T10:30:00Z",
"durationInSeconds": 5400,
"wasteDurationInSeconds": 720,
"wastePercentage": 13
},
"insights": [
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"insightType": "Idling",
"reason": "No activity detected for extended period",
"startedAtTime": "2025-01-15T09:15:00Z",
"endedAtTime": "2025-01-15T09:20:00Z",
"durationInSeconds": 300,
"version": "v1"
}
],
"offset": 0,
"limit": 10,
"total": 1
}
Get User Sessions
Retrieve sessions for a user with optional filtering.
GET /insights/1.0/users/{userId}/sessions
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
applicationId |
uuid | No | Filter sessions to a specific learning app |
startedAfter |
datetime | No | Lower bound for session start time (inclusive) |
startedBefore |
datetime | No | Upper bound for session start time (inclusive) |
category |
string | No | Comma-separated categories (e.g., Engagement,Proctoring,Cheating). Overrides default. |
isProctored |
boolean | No | Filter by proctored status. Also selects proctoring types when category is not provided. |
limit |
integer | No | Maximum items to return (1-100, default 20) |
offset |
integer | No | Number of items to skip (default 0) |
Example Request
const response = await fetch(
`https://platform.timeback.com/insights/1.0/users/${userId}/sessions?startedAfter=2025-01-01T00:00:00Z`,
{
headers: { Authorization: `Bearer ${accessToken}` },
},
);
const data = await response.json();
Example Response
{
"sessions": [
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"applicationId": "learning-app-uuid",
"startedAtTime": "2025-01-15T09:00:00Z",
"endedAtTime": "2025-01-15T10:30:00Z",
"durationInSeconds": 5400,
"wasteDurationInSeconds": 720,
"wastePercentage": 13,
"isProctored": true,
"proctoredResult": "PASSED",
"recordingStartedAtTime": "2025-01-15T09:00:05Z",
"recordingEndedAtTime": "2025-01-15T10:29:55Z",
"recordingS3Key": "recordings/2025/01/15/session-550e8400.webm"
}
],
"offset": 0,
"limit": 20,
"total": 1
}
Get Session Insights
Retrieve insights for a specific session.
GET /insights/1.0/sessions/{sessionId}
When no session row exists yet for the given sessionId (for example,
immediately after a session starts but before the asynchronous Caliper
ingestion pipeline has materialized it), this endpoint returns 200
with an empty result (insights: [], total: 0, and a default session
summary with zero duration). Polling clients should treat the empty
result as "no data yet" rather than as an error.
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
sessionId |
uuid | Yes | Session ID from the sessions list |
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
isProctored |
boolean | No | Filter insights by proctored status. Also selects proctoring types when category is not provided. |
limit |
integer | No | Maximum items to return (1-100, default 20) |
offset |
integer | No | Number of items to skip (default 0) |
type |
string | No | Comma-separated insight type slugs to filter by. Overrides category and default. |
category |
string | No | Comma-separated categories (e.g., Engagement,Proctoring,Cheating). Overrides default. |
Example Request
const sessionId = '550e8400-e29b-41d4-a716-446655440000';
const response = await fetch(`https://platform.timeback.com/insights/1.0/sessions/${sessionId}`, {
headers: { Authorization: `Bearer ${accessToken}` },
});
const data = await response.json();
Get User Insights Overview
Retrieve aggregated overview data including trends and breakdowns for a time range.
GET /insights/1.0/users/{userId}/overview
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
startedAfter |
datetime | Yes | Lower bound for time window (inclusive) |
startedBefore |
datetime | Yes | Upper bound for time window (exclusive) |
targetTimezone |
string | Yes | IANA timezone for local-day bucketing |
Example Request
const params = new URLSearchParams({
startedAfter: '2025-01-01T00:00:00Z',
startedBefore: '2025-01-08T00:00:00Z',
targetTimezone: 'America/New_York',
});
const response = await fetch(`https://platform.timeback.com/insights/1.0/users/${userId}/overview?${params}`, {
headers: { Authorization: `Bearer ${accessToken}` },
});
const data = await response.json();
Example Response
{
"summary": {
"totalDurationInSeconds": 36000,
"totalWasteDurationInSeconds": 5400,
"wastePercentage": 15,
"biggestInsight": {
"level": "OnDeviceDistractions",
"durationInSeconds": 2400,
"wastePercentage": 7
}
},
"trend": {
"buckets": [
{
"startedAtTime": "2025-01-01T00:00:00Z",
"endedAtTime": "2025-01-02T00:00:00Z",
"totalDurationInSeconds": 7200,
"wasteDurationInSeconds": 1080,
"wastePercentage": 15,
"levels": [
{ "level": "StudentNotPresent", "durationInSeconds": 300 },
{ "level": "OnDeviceDistractions", "durationInSeconds": 780 }
]
}
]
},
"breakdown": {
"levels": [
{
"level": "OnDeviceDistractions",
"durationInSeconds": 2400,
"wastePercentage": 7,
"subtypes": [
{ "insightType": "Game", "durationInSeconds": 1200 },
{ "insightType": "NonLearningContent", "durationInSeconds": 1200 }
]
}
]
}
}
Insight Types
TimeBack generates the following insight types from student activity. Use GET /insights/1.0/types to discover all available types and their metadata dynamically.
Default Behavior and Type Selection
By default, endpoints return Engagement-category insight types only. Use the category or type query params to include additional categories:
| Params passed | Types returned | Example use case |
|---|---|---|
| (none) | Engagement-category types only | Default (backward compat) |
?category=Engagement |
Engagement-category types only | Engagement-only view |
?category=Engagement,Proctoring,Cheating |
All three categories | Combined insights view |
?category=Cheating |
Cheating types only | Cheating-specific view |
?isProctored=true |
Proctoring types only (legacy) | Proctoring tab |
?type=TopicShopping,Idling |
Exact slugs only | Custom type selection |
Precedence: type > category > default. The wasteDurationInSeconds and wastePercentage fields reflect the intersection of fetched types and isWaste = true types. On student-level endpoints using the default filter, waste metrics cover Engagement-category types only. On org-level enriched sessions (GET /sessions), waste metrics cover all isWaste = true types (including proctoring and cheating). Use ?category=Engagement,Proctoring,Cheating on student-level endpoints to align waste calculations with the org-level view.
Type Metadata
Each insight type has the following properties (available via GET /insights/1.0/types):
| Field | Type | Description |
|---|---|---|
slug |
string | Unique identifier (e.g., AwayFromSeat) |
displayName |
string | Human-readable name |
category |
string | Category: Engagement, Proctoring, Cheating, Safety, Stability, Journal |
isWaste |
boolean | Whether this type counts toward waste calculations |
isContinuous |
boolean | Whether this type supports gap-based merging |
precedenceLevel |
number | null | Precedence for waste overlap resolution (1-5, null if not applicable) |
description |
string | null | Human-readable description for tooltips |
color |
string | null | Hex color for UI rendering (e.g., #FF5722) |
triggersInSessionNotification |
boolean | Whether instances of this type surface as in-session notifications to the student |
Engagement Insights (Time Off-Task)
| Type | Description |
|---|---|
AwayFromSeat |
Student not detected at their desk |
EyesOffScreen |
Student looking away from screen |
Idling |
No activity for extended period |
NonLearningContent |
Browsing non-educational content |
Socializing |
Social media or messaging activity |
IgnoringHelp |
Student dismissed or ignored coaching suggestions |
SkipAssessment |
Student skipped assessment questions |
AbandonedAssessment |
Student left assessment incomplete |
TopicShopping |
Rapidly switching between topics without progress |
Proctoring Insights
Proctoring types have isWaste = true and category = Proctoring. Their durations count toward waste calculations.
| Type | Description |
|---|---|
HelpFromAnotherPerson |
Receiving unauthorized help (legacy, unprefixed) |
UnauthorizedAppUse |
Using applications not allowed during session (legacy) |
UnauthorizedDeviceUse |
Using an unauthorized device during session (legacy) |
ProctoringHelpFromAnotherPerson |
Help from another person detected during proctored session |
ProctoringUnauthorizedAppUse |
Unauthorized app usage detected during proctored session |
ProctoringUnauthorizedDeviceUse |
Unauthorized device usage during proctored session |
Cheating Insights
Cheating types have isWaste = true and category = Cheating. They use different detection thresholds than proctoring types (e.g., "help from another person" only counts when a direct answer is provided).
| Type | Description |
|---|---|
CheatingHelpFromAnotherPerson |
Help from another person detected during a non-proctored session |
CheatingUnauthorizedAppUse |
Unauthorized app usage detected during a non-proctored session |
CheatingUnauthorizedDeviceUse |
Unauthorized device usage during a non-proctored session |
Safety Insights
| Type | Description |
|---|---|
BullyingOrHarassment |
Bullying or harassment detected |
EmbarrassingOrDefaming |
Embarrassing or defaming content |
MentalHealthDistress |
Signs of mental health distress |
PornographyOrSexualContent |
Pornographic or sexual content |
SelfHarmOrSuicide |
Self-harm or suicide-related content |
SubstanceAbuse |
Substance abuse detected |
ViolenceOrAbuse |
Violence or abuse detected |
WebcamNudity |
Nudity detected via webcam |
Overview Levels
Overview data groups insights into four levels:
| Level | Description |
|---|---|
StudentNotPresent |
Student away from seat or eyes off screen |
EnvironmentalDistractions |
External distractions (eating, socializing) |
OnDeviceDistractions |
Digital distractions (games, social media) |
LearningAppBestPractices |
Learning behavior issues (skipping, shopping) |
Looking Up Application Information
Insights and sessions include an applicationId field that identifies which learning app the data came from. To map application IDs to application names and details, use the Applications API.
Get All Applications
GET /applications/1.0
This endpoint returns all applications. Each application's sourcedId is the applicationId you'll see in insights responses.
Required scope: https://purl.imsglobal.org/spec/lti/v1p3/scope/lti.readonly
This endpoint supports filtering, pagination, sorting, and field selection. See OneRoster API Conventions for details.
Example Request
const response = await fetch('https://platform.timeback.com/applications/1.0', {
headers: { Authorization: `Bearer ${accessToken}` },
});
const data = await response.json();
Example Response
{
"applications": [
{
"sourcedId": "app-uuid",
"name": "Math Learning Suite",
"description": "Comprehensive math learning platform",
"logoUrl": "https://example.com/logo.png",
"applicationType": "learning_app",
"wasteMeter": "on",
"proctoringMode": "off"
}
],
"offset": 0,
"limit": 10,
"total": 1
}
Building an Application Lookup Map
Cache application information to efficiently display app names in your dashboards:
interface ApplicationInfo {
name: string;
logoUrl: string;
}
async function buildApplicationLookup(accessToken: string): Promise<Map<string, ApplicationInfo>> {
const response = await fetch('https://platform.timeback.com/applications/1.0?limit=100', {
headers: { Authorization: `Bearer ${accessToken}` },
});
const data = await response.json();
const applicationMap = new Map<string, ApplicationInfo>();
for (const app of data.applications) {
applicationMap.set(app.sourcedId, {
name: app.name,
logoUrl: app.logoUrl,
});
}
return applicationMap;
}
// Usage: Display application name for a session
const applicationLookup = await buildApplicationLookup(accessToken);
const session = sessions[0];
const applicationInfo = applicationLookup.get(session.applicationId);
console.log(`Session in: ${applicationInfo?.name}`); // "Session in: Math Learning Suite"
Common Use Cases
Building a Student Dashboard
Use the overview endpoint to show weekly trends:
async function getWeeklyOverview(userId: string, timezone: string): Promise<Overview> {
const now = new Date();
const weekAgo = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000);
const params = new URLSearchParams({
startedAfter: weekAgo.toISOString(),
startedBefore: now.toISOString(),
targetTimezone: timezone,
});
const response = await fetch(`https://platform.timeback.com/insights/1.0/users/${userId}/overview?${params}`, {
headers: { Authorization: `Bearer ${accessToken}` },
});
return response.json();
}
Listing Recent Sessions with Waste Stats
async function getRecentSessions(userId: string): Promise<Session[]> {
const response = await fetch(`https://platform.timeback.com/insights/1.0/users/${userId}/sessions?limit=10`, {
headers: { Authorization: `Bearer ${accessToken}` },
});
const data = await response.json();
return data.sessions;
}
Fetching All Insight Categories for a Session
// Get waste + proctoring + cheating insights in one call
async function getAllInsights(sessionId: string): Promise<Insights> {
const response = await fetch(
`https://platform.timeback.com/insights/1.0/sessions/${sessionId}?category=Engagement,Proctoring,Cheating`,
{ headers: { Authorization: `Bearer ${accessToken}` } },
);
return response.json();
}
Drilling into Session Details
async function getSessionDetails(sessionId: string): Promise<Insights> {
const response = await fetch(`https://platform.timeback.com/insights/1.0/sessions/${sessionId}`, {
headers: { Authorization: `Bearer ${accessToken}` },
});
return response.json();
}
Error Handling
The API returns standard HTTP status codes:
| Status | Description |
|---|---|
| 200 | Success |
| 400 | Bad request (invalid parameters) |
| 401 | Unauthorized (missing or invalid token) |
| 403 | Forbidden (insufficient scope) |
| 404 | Not found (user doesn't exist) |
| 500 | Internal server error |
Error responses include details:
{
"error": "Bad request",
"message": "Invalid UUID format for userId"
}
Related Docs
Level 3: Caliper Events
Insights are generated from Caliper events. Implement Level 3 first to start generating insights.
Session Management
Understand session lifecycle, auto-attach vs explicit sessions, heartbeat, and session metadata.
Authentication
Obtain OAuth client credentials and access tokens required for the Insights API.
OneRoster API Conventions
Learn about filtering, pagination, sorting, and field selection for the Applications API.
