Skip to content

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 elementBecomes
id attributethe 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:

ParameterExampleWhat to do with it
endpointhttps://app.lecturepanda.com/lrs/The LRS base URL. Same-origin, so a relative /lrs/ also works if you must hardcode.
authBearer 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.
registrationa UUIDThis learner's attempt. Send as context.registration on every statement.
activity_idhttp://example.com/courses/wellness-101Usually your statement object.id.
groupingsame as activity_idPut in context.contextActivities.grouping.
platformLecturePandaPut 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

ResourcePath
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 actor unchanged — don't substitute a different identity
  • sets context.registration to the launch registration UUID
  • sets context.platform to LecturePanda
  • sets context.contextActivities.grouping[0].id to the launch activity_id
  • uses the launch activity_id as object.id for 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

  • [ ] .zip containing tincan.xml at the root or one folder deep
  • [ ] Manifest declares the http://projecttincan.com/tincan.xsd namespace
  • [ ] The launch activity has <name>, <description> and <launch>, and the launch file exists in the zip
  • [ ] The launch activity's id is a stable, globally unique IRI
  • [ ] Content reads endpoint, auth, actor, registration and activity_id from the query string
  • [ ] Every statement carries the launch actor (converted from arrays to strings) and context.registration
  • [ ] Authorization on every LRS call is the auth value, 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