How the runtime works
A SCORM 1.2 course does not talk to an LMS over HTTP in any standardised way. It talks to a JavaScript object called API that the LMS places on the launch window (or one of its parent frames). The course finds that object, calls LMSInitialize, reads and writes data model elements with LMSGetValue and LMSSetValue, asks the LMS to persist with LMSCommit, and ends the session with LMSFinish. Everything an LMS will ever know about the attempt travels through those calls, as strings. There are no numbers, booleans, or objects on the wire — every value is a string, and every failure is signalled by the string "false" plus an error code you have to ask for separately.
Two consequences follow. First, a package can be perfectly valid and still report nothing, because it never found the API object or set values the LMS discards. Second, debugging SCORM means reading the call log — the ordered list of every LMSSetValue the course made — not the course source. That log is what our free tester shows you.
The eight API functions
| Function | Returns | Notes |
|---|---|---|
LMSInitialize("") | "true" / "false" | Must be called once, before any other call. The argument must be an empty string. |
LMSFinish("") | "true" / "false" | Ends the session. Implies a final commit on most platforms — but not all, which is why courses should commit before finishing. |
LMSGetValue(element) | the value, or "" | An empty string is both a legitimate value and the error return; check LMSGetLastError to know which. |
LMSSetValue(element, value) | "true" / "false" | Sets one element. The value is not guaranteed persisted until a commit. |
LMSCommit("") | "true" / "false" | Asks the LMS to persist everything set so far. Some platforms throttle frequent commits; see profiles below. |
LMSGetLastError() | error code string | The only way to distinguish "empty value" from "call failed". |
LMSGetErrorString(code) | short text | Human-readable version of a code. |
LMSGetDiagnostic(code) | vendor text | Vendor-specific detail; contents vary by LMS. |
cmi.core elements
The cmi.core group is the part of the data model every conformant LMS must support. Access is from the course's point of view: RO = the course may only read it, WO = only write it, RW = both.
| Element | Access | Type / values | What it is |
|---|---|---|---|
cmi.core.student_id | RO | string | The LMS's identifier for the learner. |
cmi.core.student_name | RO | string | Usually "Last, First". Courses that greet learners read this. |
cmi.core.lesson_location | RW | string, max 255 chars | The bookmark. Small — real resume state belongs in cmi.suspend_data. |
cmi.core.credit | RO | credit / no-credit | Whether the attempt counts. |
cmi.core.lesson_status | RW | passed, completed, failed, incomplete, browsed, not attempted | The one status field: completion and success share it. See 1.2 vs 2004 for why that matters. |
cmi.core.entry | RO | ab-initio, resume, "" | "resume" means a suspended attempt is being continued and suspend data is available. |
cmi.core.score.raw | RW | number in a string, 0–100 by convention | The score most LMS reports display. Set it before setting a passed/failed status. |
cmi.core.score.min / .max | RW | number in a string | Bounds for raw. Optional, and ignored by several platforms. |
cmi.core.total_time | RO | HHHH:MM:SS.SS | Accumulated time across sessions, maintained by the LMS. |
cmi.core.session_time | WO | HHHH:MM:SS.SS | This session's duration; the course writes it before finishing. |
cmi.core.lesson_mode | RO | normal, browse, review | Courses should not overwrite a real status during review mode — a classic lost-completion cause. |
cmi.core.exit | WO | time-out, suspend, logout, "" | "suspend" preserves the attempt for resume. An empty string usually ends it — and several platforms then discard suspend data. |
Beyond cmi.core
| Element | Access | Notes |
|---|---|---|
cmi.suspend_data | RW | One string, max 4,096 characters, for everything the course wants to remember between sessions. The most common silent failure in 1.2 — see suspend data limits. |
cmi.launch_data | RO | Static text from the manifest's adlcp:datafromlms, handed to the course at launch. |
cmi.comments | RW | Free text. Rarely surfaced by LMS reports. |
cmi.student_data.mastery_score | RO | From adlcp:masteryscore in the manifest. Beware: when present, many LMSs recompute passed/failed from it and overwrite the status the course set. |
cmi.student_data.max_time_allowed / .time_limit_action | RO | Time limit and what to do when it is exceeded. Enforcement varies widely. |
cmi.objectives.n.* | RW | Per-objective id, score, and status, indexed from 0. Stored by most platforms, reported by few. |
cmi.interactions.n.* | WO | Per-question records: id, type, student_response, result, latency. Write-only in 1.2 — reading them back is an error — and some platforms accept and then discard them. |
Error codes
The codes a course actually encounters in the field:
| Code | Meaning | Usual cause in practice |
|---|---|---|
0 | No error | — |
101 | General exception | The LMS's catch-all. Check the diagnostic string. |
201 | Invalid argument | Misspelled element name, or a non-empty string passed to Initialize/Commit/Finish. |
301 | Not initialized | A call made before LMSInitialize succeeded — often a race at launch. |
401 | Not implemented | An optional element this LMS chose not to support. |
402 | Invalid set — element is a keyword | Setting a group name like cmi.core instead of a leaf element. |
403 | Element is read only | Trying to set cmi.core.total_time, entry, and so on. |
404 | Element is write only | Reading cmi.interactions or session_time back. |
405 | Incorrect data type | A status value outside the vocabulary, a non-numeric score, a malformed timespan. |
Where real LMSs differ
The specification leaves room, and platforms use it. The differences that produce most support tickets: whether an empty cmi.core.exit discards suspend data or quietly keeps it; whether mastery_score overrides the status the course set; whether interactions are stored or accepted-and-dropped; how strictly score values are validated (some platforms reject "85.0" where others accept it); and how often LMSCommit may be called before the platform starts ignoring or throttling it. These behavioural differences are exactly what our saved runtime behaviour profiles replay a package against, and the reason "it worked in one LMS" proves little about the next one.