Appearance
xAPI Modules
For content authors packaging xAPI content to run inside LecturePanda. Covers the package format, the launch contract, and the LRS endpoints your content talks to.
LecturePanda is xAPI 1.0.3 compliant and hosts your content itself: you upload a .zip, LecturePanda extracts and serves it, and the Learning Record Store runs on the same origin as the served files.
Building the course, not the content?
If you're troubleshooting a module that won't complete rather than authoring one, Storyline & xAPI Modules is the page you want.
The one thing that surprises most authors
There are no LRS credentials to configure. LecturePanda mints a short-lived token at launch time and passes it to your content in the launch URL.
So when you publish from your authoring tool:
- Leave the LRS endpoint and credential fields blank.
- Turn off any "prompt for name and email" option — identity arrives in the launch URL too.
Content that hardcodes an endpoint, ships credentials, or asks the learner who they are will not work correctly here.
Package format
Standalone xAPI (Tin Can API) .zip packages with a tincan.xml manifest.
cmi5 is not supported — a package containing only cmi5.xml won't load. Neither is SCORM 1.2 or 2004; see SCORM to xAPI conversion if that's what you have.
mycourse.zip
├── tincan.xml manifest — at the root, or one folder deep
├── index.html the launch file named in <launch>
├── assets/
│ ├── styles.css
│ └── images/...
└── ...The manifest is found by filename. If it sits inside a single top-level folder, that folder is stripped on upload — so every path in the manifest must be relative to the manifest's own directory. Keep filenames URL-safe; spaces and non-ASCII characters are percent-encoded when served.
The manifest
xml
<?xml version="1.0" encoding="UTF-8"?>
<tincan xmlns="http://projecttincan.com/tincan.xsd">
<activities>
<activity id="http://example.com/courses/wellness-101"
type="http://adlnet.gov/expapi/activities/course">
<name lang="en-US">Employee Health and Wellness</name>
<description lang="en-US">A short introduction to workplace wellness.</description>
<launch lang="en-US">index.html</launch>
</activity>
</activities>
</tincan>On upload LecturePanda scans the activities and picks the first one containing a <launch> element. From it, four values are read:
| Manifest element | Becomes |
|---|---|
id attribute | the activity_id passed to your content |
<launch> | the file opened at launch |
<name> | the module's displayed name |
<description> | the module's displayed description |
name, description and launch are all required
The launch activity must carry all three elements. A manifest missing <name> or <description> fails on upload rather than falling back to a default.
Additional activities — modules, interactions — are preserved and free to reference in your statements; they just aren't surfaced separately in the admin interface.
Activity IDs must be valid IRIs, should be globally unique to your course (prefix with a URL you control), and should stay stable across versions if you want learner progress to survive a re-upload.
The launch contract
Launches are built server-side and look like this:
https://<lecturepanda-host>/modules/<content-token>/<launch-file>?<params>Your content must parse these query-string parameters on startup:
| Parameter | Example | What to do with it |
|---|---|---|
endpoint | https://app.lecturepanda.com/lrs/ | The LRS base URL. Same-origin, so a relative /lrs/ also works if you must hardcode. |
auth | Bearer eyJhbGci... | Forward verbatim as the Authorization header on every LRS call. |
actor | {"name":["Jane Doe"],"mbox":["mailto:jane@example.com"]} | JSON-encoded xAPI Agent. Use as the actor on every statement. |
registration | a UUID | This learner's attempt. Send as context.registration on every statement. |
activity_id | http://example.com/courses/wellness-101 | Usually your statement object.id. |
grouping | same as activity_id | Put in context.contextActivities.grouping. |
platform | LecturePanda | Put in context.platform. |
Identity is always supplied this way, and always mbox-based (mailto:) — LecturePanda doesn't issue account-style actor identifiers. If actor or registration is missing, treat it as a launch error and say so; never fall back to a prompt or an anonymous mode.
The actor arrives with array values, statements need strings
The actor launch parameter encodes name and mbox as single-element arrays. xAPI statements require strings. Convert on the way through.
Articulate Storyline, Rise and Adobe Captivate handle this for you, so it mostly bites hand-built packages. When it does, everything looks fine — the module launches and renders — but every statement is rejected with a 400 mentioning a list where a string was expected, and no completion ever registers.
Token lifetime is 12 hours. Don't assume a longer session; detect an expired token on a long-running attempt and ask the learner to relaunch.
LRS endpoints
| Resource | Path |
|---|---|
| Statements | /lrs/statements (POST / GET / PUT) |
| Agents | /lrs/agents |
| Agent Profile | /lrs/agents/profile |
| Activities | /lrs/activities |
| Activity Profile | /lrs/activities/profile |
| Activity State | /lrs/activities/state |
| About | /lrs/about (GET) |
Every request must carry an X-Experience-API-Version header between 1.0.0 and 1.0.3. Requests without one are rejected with a 400.
Statements
Standard xAPI 1.0.3. Beyond ordinary validation, make sure each statement:
- carries the launch
actorunchanged — don't substitute a different identity - sets
context.registrationto the launchregistrationUUID - sets
context.platformtoLecturePanda - sets
context.contextActivities.grouping[0].idto the launchactivity_id - uses the launch
activity_idasobject.idfor course-level statements, or a child activity IRI for sub-activity ones
Statement IDs may be your own UUIDs, or omitted for the LRS to assign.
State (bookmarking and resume)
GET / PUT / DELETE on /lrs/activities/state, with activityId, agent (the launch actor), registration (the launch UUID) and your own stateId key — bookmark, suspend_data, whatever suits. This is the supported way to persist progress between sessions.
Authoring tool compatibility
Any tool that exports "Tin Can API" or "xAPI 1.0" should work:
- Articulate Storyline — Tin Can API publish target
- Articulate Rise — xAPI publish, not cmi5
- Adobe Captivate — Tin Can API publish target
- iSpring Suite — Experience API (Tin Can) output
- Lectora — Tin Can publish target
Any export producing only cmi5.xml will not.
Before you send a package
- [ ]
.zipcontainingtincan.xmlat the root or one folder deep - [ ] Manifest declares the
http://projecttincan.com/tincan.xsdnamespace - [ ] The launch activity has
<name>,<description>and<launch>, and the launch file exists in the zip - [ ] The launch activity's
idis a stable, globally unique IRI - [ ] Content reads
endpoint,auth,actor,registrationandactivity_idfrom the query string - [ ] Every statement carries the launch actor (converted from arrays to strings) and
context.registration - [ ]
Authorizationon every LRS call is theauthvalue, unmodified - [ ] No hardcoded LRS URLs, credentials or learner identities anywhere
- [ ] No name/email prompt and no anonymous fallback
- [ ] No dependency on cmi5 features
- [ ] Sessions longer than 12 hours detect the expired token and prompt a relaunch
Related
- Storyline & xAPI Modules — diagnosing a module that won't complete
- Materials — where modules are uploaded to a course
- OAuth API — the REST API for registrations and reporting