xAPI Integration Guide
Overview
Creatiq includes built-in xAPI (Experience API) support that automatically tracks learner interactions with H5P content. Every time a user plays, answers, completes, or fails an H5P activity, Creatiq generates xAPI statements conforming to the xAPI 1.0.3 specification and stores them in PostgreSQL.
Key capabilities:
- Automatic statement generation from H5P player events (no manual instrumentation needed)
- 7 supported verbs covering the full learning interaction lifecycle
- Built-in analytics with content-level, learner-level, and class-level aggregations
- Statement querying API with filters for actor, verb, object, and time range
- Event emitter for real-time forwarding to external LRS systems
All xAPI endpoints require authentication via Bearer token (Authorization: Bearer <token>).
Statement Format
Creatiq xAPI statements follow the standard structure:
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"actor": {
"objectType": "Agent",
"account": {
"homePage": "https://creatiq.app",
"name": "user-uuid-here"
}
},
"verb": {
"id": "http://adlnet.gov/expapi/verbs/completed",
"display": { "en-US": "completed" }
},
"object": {
"objectType": "Activity",
"id": "https://creatiq.app/h5p/content/content-id-here",
"definition": {
"type": "http://adlnet.gov/expapi/activities/assessment"
}
},
"result": {
"score": {
"scaled": 0.85,
"raw": 17,
"max": 20
},
"success": true,
"completion": true,
"duration": "PT120S",
"response": "answer-data"
},
"context": {
"platform": "EduAgentic H5P",
"language": "en"
},
"timestamp": "2026-03-25T10:30:00.000Z",
"version": "1.0.3"
}
Actor
All actors use the account identifier type. The homePage is derived from the NEXT_PUBLIC_BASE_URL environment variable (defaults to https://eduagentic.ai), and name is set to the authenticated user's UUID. When statements are submitted via POST /api/xapi/statements, the actor's account is automatically enriched from the authenticated session.
Verb
Each verb includes an id (IRI) and a display map. See the Supported Verbs section below.
Object
The object id follows the pattern {BASE_URL}/h5p/content/{contentId}. The definition.type is set to http://adlnet.gov/expapi/activities/assessment for all H5P content.
Result
The result object is optional and contains:
| Field | Type | Description |
|---|---|---|
score.scaled | number (0-1) | Normalized score |
score.raw | number | Raw score value |
score.max | number | Maximum possible score |
score.min | number | Minimum possible score |
success | boolean | Whether the learner succeeded |
completion | boolean | Whether the activity was completed |
duration | string | ISO 8601 duration (e.g., PT120S) |
response | string | Learner's response data |
Context
The context object includes:
| Field | Type | Description |
|---|---|---|
platform | string | Always "EduAgentic H5P" |
language | string | Content language code |
registration | string | Registration UUID (optional) |
contextActivities | object | Parent, grouping, category activities (optional) |
Supported Verbs
Creatiq recognizes and maps the following 7 xAPI verbs:
| Verb | IRI | Description |
|---|---|---|
| attempted | http://adlnet.gov/expapi/verbs/attempted | Learner started an activity |
| answered | http://adlnet.gov/expapi/verbs/answered | Learner submitted an answer to a question |
| completed | http://adlnet.gov/expapi/verbs/completed | Learner finished the activity |
| passed | http://adlnet.gov/expapi/verbs/passed | Learner met the passing criteria |
| failed | http://adlnet.gov/expapi/verbs/failed | Learner did not meet the passing criteria |
| interacted | http://adlnet.gov/expapi/verbs/interacted | Learner interacted with the content (generic) |
| progressed | http://adlnet.gov/expapi/verbs/progressed | Learner made progress within the activity |
When an unrecognized verb is received from H5P, it falls back to interacted.
Auto-Generated Statements
H5P content types automatically emit xAPI events through the H5P runtime. When a user plays content via the Creatiq embed player (/embed/{contentId}), the player intercepts these events and:
- Extracts the verb name from the xAPI verb IRI (last path segment)
- Parses the result object (score, success, completion, duration)
- Sends the data to
POST /api/xapi/h5p-eventwith the authenticated token - Simultaneously forwards the raw statement to the parent window via
postMessage(for SDK consumers)
H5P Events That Generate Statements
| H5P Content Type | Events Generated |
|---|---|
| Multiple Choice | attempted, answered, completed, passed/failed |
| True/False | attempted, answered, completed, passed/failed |
| Fill in the Blanks | attempted, answered, completed, passed/failed |
| Drag and Drop | attempted, interacted, completed, passed/failed |
| Interactive Video | attempted, interacted, progressed, answered, completed |
| Course Presentation | attempted, progressed, answered, completed |
| Question Set | attempted, answered, completed, passed/failed |
| Summary | attempted, answered, completed |
| Essay | attempted, answered, completed |
| Dialog Cards | attempted, interacted, completed |
SDK postMessage Events
When content is embedded via the SDK <CreatiqPlayer> component or a raw iframe, xAPI statements are also forwarded to the parent window:
// Message format sent to parent window
{
type: 'creatiq:xapi',
contentId: 'abc123',
statement: { /* full xAPI statement */ }
}
Listen for these in your host application:
window.addEventListener('message', (event) => {
if (event.data?.type === 'creatiq:xapi') {
const { contentId, statement } = event.data;
// Forward to your own LRS, analytics pipeline, etc.
}
});
LRS Configuration
Creatiq stores all xAPI statements in its own PostgreSQL database. To forward statements to an external Learning Record Store (LRS), use the XAPIService event emitter on the server side:
Server-Side Forwarding
The xapiService instance emits a statement event every time a statement is successfully stored:
import { xapiService } from './services/xapi';
xapiService.on('statement', async (statement) => {
// Forward to external LRS
await fetch('https://your-lrs.example.com/xapi/statements', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Basic ' + btoa('key:secret'),
'X-Experience-API-Version': '1.0.3',
},
body: JSON.stringify(statement),
});
});
Client-Side Forwarding via SDK
If you are using the <CreatiqPlayer> SDK component, use the onXAPIStatement callback:
<CreatiqPlayer
contentId="abc123"
onXAPIStatement={(statement) => {
// Send to your LRS endpoint
fetch('/api/lrs/statements', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(statement),
});
}}
/>
Supported LRS Platforms
Creatiq xAPI statements are compatible with any LRS that supports xAPI 1.0.3:
- Learning Locker
- SCORM Cloud
- Watershed LRS
- Yet Analytics
- Veracity Learning
Querying Statements -- API Endpoints
Store Statements
POST /api/xapi/statements
Authorization: Bearer <token>
Content-Type: application/json
Body: A single statement object or an array of statements.
Response:
{
"success": true,
"data": { "ids": ["uuid-1", "uuid-2"] }
}
Store H5P Event (Simplified)
POST /api/xapi/h5p-event
Authorization: Bearer <token>
Content-Type: application/json
Body:
{
"contentId": "abc123",
"verb": "completed",
"score": { "scaled": 0.85, "raw": 17, "max": 20 },
"success": true,
"completion": true,
"duration": 120,
"response": "answer-data"
}
Required fields: contentId, verb. All other fields are optional.
Response:
{
"success": true,
"data": { "id": "generated-uuid" }
}
Query Statements
GET /api/xapi/statements
Authorization: Bearer <token>
Query Parameters:
| Parameter | Type | Description |
|---|---|---|
actorId | string | Filter by actor account name or mbox |
verbId | string | Filter by verb IRI (e.g., http://adlnet.gov/expapi/verbs/completed) |
objectId | string | Filter by object IRI |
since | ISO 8601 | Statements after this timestamp |
until | ISO 8601 | Statements before this timestamp |
limit | number | Maximum number of statements to return |
Response:
{
"success": true,
"data": {
"statements": [ /* array of xAPI statements */ ]
}
}
Content Analytics
GET /api/xapi/analytics/content/:contentId
Authorization: Bearer <token>
Response:
{
"success": true,
"data": {
"contentId": "abc123",
"totalAttempts": 150,
"totalCompletions": 120,
"averageScore": 78.5,
"averageDuration": 0,
"passRate": 85.0,
"lastUpdated": "2026-03-25T10:30:00.000Z"
}
}
User Analytics
GET /api/xapi/analytics/user
Authorization: Bearer <token>
Returns aggregated analytics for the authenticated user.
Response:
{
"success": true,
"data": {
"userId": "user-uuid",
"totalContentsAttempted": 25,
"totalContentsCompleted": 20,
"averageScore": 82.3,
"strongAreas": ["content-id-1", "content-id-2"],
"weakAreas": ["content-id-3"],
"learningTime": 0
}
}
User Progress (Single Content)
GET /api/xapi/progress/:contentId
Authorization: Bearer <token>
Response:
{
"success": true,
"data": {
"userId": "user-uuid",
"contentId": "abc123",
"attempts": 3,
"bestScore": 95.0,
"lastScore": 0,
"completed": true,
"passed": true,
"totalDuration": 0,
"lastAttemptAt": "2026-03-25T10:30:00.000Z"
}
}
User Progress (All Content)
GET /api/xapi/progress
Authorization: Bearer <token>
Response:
{
"success": true,
"data": {
"progress": [
{
"userId": "user-uuid",
"contentId": "abc123",
"attempts": 3,
"bestScore": 95.0,
"lastScore": 0,
"completed": true,
"passed": true,
"totalDuration": 0,
"lastAttemptAt": "2026-03-25T10:30:00.000Z"
}
]
}
}
Data Schema
Creatiq stores xAPI statements in the xapi_statements table with the following structure:
| Column | Type | Description |
|---|---|---|
id | UUID | Statement ID (primary key) |
actor | JSONB | Actor object |
verb | JSONB | Verb object |
object | JSONB | Activity object |
result | JSONB | Result object (nullable) |
context | JSONB | Context object (nullable) |
timestamp | TIMESTAMPTZ | Statement timestamp |
stored | TIMESTAMPTZ | When the statement was stored |
authority | JSONB | Authority object (nullable) |
version | VARCHAR(20) | xAPI version (default: 1.0.3) |
full_statement | JSONB | Complete statement for retrieval |
Indexed fields: actor.account.name (GIN), verb.id, object.id, timestamp (DESC).
Duplicate statements (same id) are silently ignored via ON CONFLICT DO NOTHING.