> ## Documentation Index
> Fetch the complete documentation index at: https://docs.timeback.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Caliper

> Caliper Analytics API client for learning events

## 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

<CodeGroup>
  ```bash npm theme={null}
  npm install @timeback/caliper
  ```

  ```bash pnpm theme={null}
  pnpm add @timeback/caliper
  ```

  ```bash yarn theme={null}
  yarn add @timeback/caliper
  ```

  ```bash bun theme={null}
  bun add @timeback/caliper
  ```

  ```bash pip theme={null}
  pip install timeback-caliper
  ```

  ```bash uv theme={null}
  uv add timeback-caliper
  ```
</CodeGroup>

## Quick Start

<CodeGroup>
  ```typescript TypeScript theme={null}
  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)
  ```

  ```python Python theme={null}
  from timeback_caliper import CaliperClient, ActivityCompletedInput

  client = CaliperClient(
      env="staging",
      client_id=os.environ["CALIPER_CLIENT_ID"],
      client_secret=os.environ["CALIPER_CLIENT_SECRET"],
  )

  result = await client.events.send_activity("https://myapp.example.com/sensors/main",
      ActivityCompletedInput(
          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},
          ],
      ),
  )

  status = await client.jobs.wait_for_completion(result.job_id)
  ```
</CodeGroup>

## Client Structure

```typescript theme={null}
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:

<CodeGroup>
  ```typescript TypeScript theme={null}
  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 },
  	],
  })
  ```

  ```python Python theme={null}
  from timeback_caliper import ActivityCompletedInput

  await client.events.send_activity(sensor_url,
      ActivityCompletedInput(
          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": "Math", "app": {"name": "My Learning App"}},
          metrics=[
              {"type": "totalQuestions", "value": 10},
              {"type": "correctQuestions", "value": 8},
              {"type": "xpEarned", "value": 150},
              {"type": "masteredUnits", "value": 2},
          ],
      ),
  )
  ```
</CodeGroup>

### Metric Types

| Type               | Description                         |
| ------------------ | ----------------------------------- |
| `totalQuestions`   | Total number of questions attempted |
| `correctQuestions` | Number of correct answers           |
| `xpEarned`         | Experience points earned            |
| `masteredUnits`    | Number of units mastered            |

## Time Spent Events

Track time spent on activities:

<CodeGroup>
  ```typescript TypeScript theme={null}
  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
  	],
  })
  ```

  ```python Python theme={null}
  from timeback_caliper import TimeSpentInput

  await client.events.send_time_spent(sensor_url,
      TimeSpentInput(
          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
          ],
      ),
  )
  ```
</CodeGroup>

## Job Monitoring

Event processing is asynchronous. Monitor job status:

<CodeGroup>
  ```typescript TypeScript theme={null}
  client.events.sendActivity(sensorUrl, activityData) // Returns { jobId }
  client.jobs.getStatus(jobId)
  client.jobs.waitForCompletion(jobId)
  ```

  ```python Python theme={null}
  await client.events.send_activity(sensor_url, activity_data)  # Returns { job_id }
  await client.jobs.get_status(job_id)
  await client.jobs.wait_for_completion(job_id)
  ```
</CodeGroup>

| Method                | Returns     | Description                                       |
| --------------------- | ----------- | ------------------------------------------------- |
| `sendActivity()`      | `{ jobId }` | Send events, get job ID                           |
| `getStatus()`         | `JobStatus` | Check status: pending/processing/completed/failed |
| `waitForCompletion()` | `JobStatus` | Poll until job completes                          |

## Query Events

Retrieve stored events:

<CodeGroup>
  ```typescript TypeScript theme={null}
  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()
  ```

  ```python Python theme={null}
  await client.events.list(limit=..., offset=..., sensor=..., actor_id=..., start_date=..., end_date=...)
  await client.events.get(external_id)
  async for event in client.events.stream(start_date=..., max_items=...):
      ...
  ```
</CodeGroup>

## Batching Events

Use factory functions to combine multiple events in a single API call:

```typescript theme={null}
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:

```typescript theme={null}
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

```typescript theme={null}
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:

```typescript theme={null}
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

<CardGroup cols={2}>
  <Card title="Custom Activities" icon="chart-line" href="/beta/build-on-timeback/sdk/activity-tracking/intro">
    SDK-based activity tracking
  </Card>

  <Card title="EduBridge" icon="bridge" href="/beta/build-on-timeback/clients/edubridge">
    Pre-aggregated analytics
  </Card>

  <Card title="Types" icon="brackets-curly" href="/beta/api-reference/overview">
    Caliper type definitions
  </Card>
</CardGroup>
