Skip to main content

Overview

The Caliper client provides access to the Caliper Analytics API, enabling:
  • Activity Events: Track learning activity completions with metrics
  • Time Spent Events: Record time spent on activities
  • Generic Events: Send any IMS Caliper v1.2 event
  • Batching: Combine multiple events in a single API call
  • Job Monitoring: Track asynchronous event processing

Installation

npm install @timeback/caliper

Quick Start

import { CaliperClient } from '@timeback/caliper'

const client = new CaliperClient({
	env: 'staging',
	auth: {
		clientId: process.env.CALIPER_CLIENT_ID!,
		clientSecret: process.env.CALIPER_CLIENT_SECRET!,
	},
})

const result = await client.events.sendActivity('https://myapp.example.com/sensors/main', {
	actor: {
		id: 'https://api.example.com/users/123',
		type: 'TimebackUser',
		email: 'student@example.edu',
	},
	object: {
		id: 'https://myapp.example.com/activities/456',
		type: 'TimebackActivityContext',
		subject: 'Math',
		app: { name: 'My Learning App' },
	},
	metrics: [
		{ type: 'totalQuestions', value: 10 },
		{ type: 'correctQuestions', value: 8 },
		{ type: 'xpEarned', value: 150 },
	],
})

const status = await client.jobs.waitForCompletion(result.jobId)

Client Structure

const client = new CaliperClient(options)

// Events
client.events.send(sensor, events)                  // Send events
client.events.sendEnvelope(envelope)                // Send raw envelope
client.events.validate(envelope)                    // Validate without storing
client.events.sendActivity(sensor, input)           // Timeback Profile
client.events.sendTimeSpent(sensor, input)          // Timeback Profile
client.events.list(params?)                         // List events
client.events.get(externalId)                       // Get event by ID
client.events.stream(params?)                       // Stream events with pagination

// Jobs
client.jobs.getStatus(jobId)                        // Check job status
client.jobs.waitForCompletion(jobId, options?)      // Poll until complete

Activity Completed Events

Track when users complete learning activities:
await client.events.sendActivity(sensorUrl, {
	actor: {
		id: 'https://api.example.com/users/123',
		type: 'TimebackUser',
		email: 'student@example.edu',
		role: 'student',
	},
	object: {
		id: 'https://myapp.example.com/activities/lesson-1',
		type: 'TimebackActivityContext',
		subject: 'Math',
		app: { name: 'My Learning App' },
		course: { name: 'Algebra 101' },
		activity: { name: 'Chapter 1 Quiz' },
	},
	attempt: 1,
	generatedExtensions: { pctCompleteApp: 67 },
	metrics: [
		{ type: 'totalQuestions', value: 10 },
		{ type: 'correctQuestions', value: 8 },
		{ type: 'xpEarned', value: 150 },
		{ type: 'masteredUnits', value: 2 },
	],
})

Metric Types

TypeDescription
totalQuestionsTotal number of questions attempted
correctQuestionsNumber of correct answers
xpEarnedExperience points earned
masteredUnitsNumber of units mastered

Time Spent Events

Track time spent on activities:
await client.events.sendTimeSpent(sensorUrl, {
	actor: {
		id: 'https://api.example.com/users/123',
		type: 'TimebackUser',
		email: 'student@example.edu',
	},
	object: {
		id: 'https://myapp.example.com/activities/lesson-1',
		type: 'TimebackActivityContext',
		subject: 'Reading',
		app: { name: 'My App' },
	},
	metrics: [
		{ type: 'active', value: 1800 }, // 30 minutes in seconds
		{ type: 'inactive', value: 300 }, // 5 minutes paused
	],
})

Job Monitoring

Event processing is asynchronous. Monitor job status:
client.events.sendActivity(sensorUrl, activityData) // Returns { jobId }
client.jobs.getStatus(jobId)
client.jobs.waitForCompletion(jobId)
MethodReturnsDescription
sendActivity(){ jobId }Send events, get job ID
getStatus()JobStatusCheck status: pending/processing/completed/failed
waitForCompletion()JobStatusPoll until job completes

Query Events

Retrieve stored events:
client.events.list({ limit?, offset?, sensor?, actorId?, actorEmail?, startDate?, endDate? })
client.events.get(externalId)
client.events.stream({ startDate?, max? })
client.events.stream({ startDate?, max? }).toArray()

Batching Events

Use factory functions to combine multiple events in a single API call:
import { CaliperClient, createActivityEvent, createTimeSpentEvent } from '@timeback/caliper'

const activityEvent = createActivityEvent({
	actor: { ... },
	object: { ... },
	metrics: [{ type: 'correctQuestions', value: 8 }],
	attempt: 2,
	generatedExtensions: { pctCompleteApp: 67 },
})

const timeSpentEvent = createTimeSpentEvent({
	actor: { ... },
	object: { ... },
	metrics: [{ type: 'active', value: 1800 }],
})

// Send both in one API call
const result = await client.events.send(sensor, [activityEvent, timeSpentEvent])

Generic Events

For non-Timeback events, use the generic API:
const event = {
	id: 'urn:uuid:...',
	type: 'NavigationEvent',
	actor: 'https://example.edu/users/123',
	action: 'NavigatedTo',
	object: 'https://example.edu/courses/456',
	eventTime: new Date().toISOString(),
	profile: 'GeneralProfile',
}

await client.events.send(sensor, [event])

Error Handling

import { ApiError } from '@timeback/caliper'

try {
	await client.events.get('invalid-id')
} catch (error) {
	if (error instanceof ApiError) {
		console.log(error.statusCode)
		console.log(error.message)
	}
}

SDK Integration

When using the full SDK, activity tracking automatically emits Caliper events:
import { createTimeback } from '@timeback/sdk'

const timeback = await createTimeback({
	/* ... */
})

const activity = timeback.activity.start({
	id: 'lesson-1',
	name: 'Introduction',
	course: { subject: 'Math', grade: 3 },
})

await activity.end({
	totalQuestions: 10,
	correctQuestions: 8,
	xpEarned: 80,
}) // Emits ActivityCompleted + TimeSpent events

Next Steps

Custom Activities

SDK-based activity tracking

EduBridge

Pre-aggregated analytics

Types

Caliper type definitions