Diagnose xAPI statements and LRS connectivity. Three subcommands. Zero runtime dependencies. Built because the most common xAPI failures — a malformed statement, a missing X-Experience-API-Version header, an LRS that 401s for reasons the dashboard doesn't explain — should be findable in seconds, not in an LRS log dive.
npm install -g github:parthdhanani/xapi-doctor # installs from source (not yet on npm)
xapi-doctor lint statement.json --profile cmi5
xapi-doctor ping https://lrs.example.com/data/xAPI/ --auth user:pass
xapi-doctor send statements.json https://lrs.example.com/data/xAPI/ --auth user:passExit codes: 0 clean, 1 warnings only, 2 errors. Every command supports --json for CI pipelines.
cmi5 and xAPI are the spec stack most enterprise LMS RFPs ask for in 2026. The toolchain around them is thin:
- The ADL conformance tester exists but is a complete-spec test rig, not a developer's tool.
- LRS vendors offer "send a statement" debug UIs, but those don't tell you which field of your existing statement is wrong.
- The first signal that something is broken is usually a 400 from the LRS with a body like
{"message":"Invalid request"}and nothing else.
xapi-doctor is the small CLI for the moments when you have a statement file or an LRS endpoint and you need a fast, specific answer. It's the diagnostic counterpart to scorm-kit, which handles SCORM / cmi5 packaging.
Validates an xAPI statement (or an array of them) against the xAPI 1.0.3 spec. Catches:
- Missing required fields (
actor,verb,object) - Non-UUID statement ids and registrations
- ISO-8601 timestamp and duration violations
- IRI shape on
verb.id,object.id,object.definition.type - Actor IFI rules: exactly one of
mbox/mbox_sha1sum/openid/account mailto:shape onactor.mbox; full account shape onactor.account- Score range:
scaledin[-1, 1],rawbetweenminandmax - Context misuse:
revision/platformon Agent objects, non-UUIDregistration verb.displaylanguage map presence andenentry
With --profile cmi5, adds the cmi5-specific constraints:
- cmi5 reserved verbs (Launched / Initialized / Completed / Passed / Failed / Abandoned / Waived / Terminated / Satisfied) require
context.registration Passed/Failed/Completedrequire thehttps://w3id.org/xapi/cmi5/context/categories/moveoncategory activityactor.mboxwithoutactor.accountis flagged — cmi5 LMSs always launch with theaccountIFI, and anmbox-only statement will likely not match the LMS-assigned learner
Verifies that an LRS is reachable and behaving like a 1.0.3-compliant LRS:
- DNS, TCP, TLS reachability of the base URL
GET /aboutreturns a version array1.0.3is among the advertised versionsAccess-Control-Allow-Originis present (browser-based clients fail silently without it)- With
--auth, performs an authenticatedGET /statements?limit=1and reports 200 / 401 / 403 distinctly - LRS echoes
X-Experience-API-Versionper §6.2 of the spec
Lints the statement(s) locally, then POSTs them to the LRS and reports the result. Refuses to POST a statement that would fail lint (use --no-validate to override). On non-2xx, decodes the most common LRS responses into human-readable explanations — 400 / 401 / 403 / 404 / 409 / 413 / 415 / 5xx.
The --profile cmi5 flag is forwarded to the lint pass.
- scorm-kit — the SCORM / cmi5 build pipeline.
scorm-kit cmi5 convertproduces a cmi5 package; once it's launched and statements are flowing,xapi-doctoris what tells you whether the statements are well-formed and whether the LRS is healthy.
- A full LRS conformance suite. The ADL test rig exists for that.
- A statement generator. It validates and forwards; it does not produce statements.
- An LRS. For development you want a real one — Veracity, Yet, or Learning Locker — running locally in Docker.
git clone https://github.com/parthdhanani/xapi-doctor
cd xapi-doctor
npm testZero runtime deps; npm install is not required to run.
17 tests in test/run.js. The HTTP tests spin up a mock LRS on a random local port and exercise ping / send against it.
npm testMIT. See LICENSE.
26 xAPI 1.0.3 rules + 4 cmi5 profile rules (enabled with --profile cmi5). Each rule maps to a spec section.
| # | ID | Level | What it checks | Spec ref |
|---|---|---|---|---|
| 1 | stmt-actor-missing |
error | statement.actor is required |
§2.3 |
| 2 | stmt-verb-missing |
error | statement.verb is required |
§2.3 |
| 3 | stmt-object-missing |
error | statement.object is required |
§2.3 |
| 4 | stmt-id-not-uuid |
error | statement.id must be a UUID (RFC 4122) |
§4.1.1 |
| 5 | stmt-timestamp-bad |
error | statement.timestamp must be ISO-8601 with timezone |
§4.1.2 |
| 6 | stmt-stored-bad |
error | statement.stored must be ISO-8601 with timezone |
§4.1.2 |
| 7 | stmt-version-bad |
warning | statement.version must look like 1.0.x |
§4.1.10 |
| 8 | actor-objectType-bad |
error | actor.objectType, if present, must be Agent or Group |
§2.4.1 |
| 9 | actor-no-ifi |
error | Actor needs exactly one IFI: mbox, mbox_sha1sum, openid, or account |
§2.4.1 |
| 10 | actor-multiple-ifi |
error | Actor has more than one IFI — exactly one required | §2.4.1 |
| 11 | actor-mbox-bad |
error | actor.mbox must be mailto:user@host.tld |
§2.4.1 |
| 12 | actor-account-bad |
error | actor.account requires homePage (IRI) and name (string) |
§2.4.1 |
| 13 | actor-openid-bad |
error | actor.openid must be an IRI |
§2.4.1 |
| 14 | verb-id-missing |
error | verb.id is required |
§2.4.2 |
| 15 | verb-id-not-iri |
error | verb.id must be an IRI |
§2.4.2 |
| 16 | verb-display-missing |
warning | verb.display is a strongly-recommended language map |
§2.4.2 |
| 17 | verb-display-lang |
warning | verb.display should include an en entry |
§2.4.2 |
| 18 | object-id-missing |
error | object.id is required |
§2.4.4 |
| 19 | object-id-not-iri |
error | object.id must be an IRI |
§2.4.4 |
| 20 | object-objectType-bad |
warning | object.objectType, when set, should be Activity for SCORM-style statements |
§2.4.4 |
| 21 | object-def-type-bad |
error | object.definition.type must be an IRI when present |
§2.4.4 |
| 22 | result-score-range |
error | result.score.scaled in [-1.0, 1.0]; raw between min and max |
§2.4.5 |
| 23 | result-duration-bad |
error | result.duration must be ISO-8601 duration (e.g. PT4M33S) |
§2.4.5 |
| 24 | ctx-registration-uuid |
error | context.registration must be a UUID |
§2.4.6 |
| 25 | ctx-revision-on-agent |
error | context.revision is only valid when object is an Activity |
§2.4.6 |
| 26 | ctx-platform-on-agent |
error | context.platform is only valid when object is an Activity |
§2.4.6 |
| # | ID | Level | What it checks | Spec ref |
|---|---|---|---|---|
| 27 | cmi5-reserved-verb |
warning | cmi5 reserved verbs (Launched / Initialized / Completed / Passed / Failed / Abandoned / Waived / Terminated / Satisfied) used outside the correct lifecycle position | cmi5 §9.3 |
| 28 | cmi5-no-registration |
error | cmi5 statements must carry context.registration (the launch session) |
cmi5 §10.0 |
| 29 | cmi5-cat-activity |
warning | Passed / Failed / Completed must include the https://w3id.org/xapi/cmi5/context/categories/moveon category activity |
cmi5 §10.0 |
| 30 | cmi5-no-account |
warning | cmi5 LMSs always use actor.account (not mbox) — mbox-only actor may not match the LMS-assigned learner |
cmi5 §8.1.3 |