# LMS packages: SCORM and xAPI reporting

> Download a simulation or coach as a SCORM or xAPI package, and see exactly what it reports to your LMS and when.

- Plans: Core, Team, Enterprise
- Canonical: https://docs.devlin.ai/publish-and-embed/lms-packages-scorm-and-xapi

An LMS package is a small zip file you upload to your LMS so a published simulation or coach launches as its own course item and reports back to the LMS. Packages, and LMS reporting from a Storyline course, are available on these plans: Core, Team, Enterprise.

There are two ways to get a simulation's result into an LMS:

- A standalone package. The simulation is the whole course item. Download the package from the simulation's **Publish** stage. This page covers it first.
- A Storyline course that contains the simulation. The course is the LMS item, and one line of JavaScript, `sendResultsToLMS()`, writes the simulation's result into the course's SCORM session. The last section of this page covers it.

Reporting from a Rise 360 course is set up in the **Rise 360** section of the same stage; see [Embedding a simulation in Rise 360](https://docs.devlin.ai/publish-and-embed/rise-360). To wrap or host a whole eLearning file that is not a simulation, see [Hosting and SCORM Wrap](https://docs.devlin.ai/publish-and-embed/hosting-and-scorm-wrap). Packages downloaded from a hosted project have their own plan list, which that page states.

## Download a simulation package

1. Publish the simulation. Until it is published, the section shows "Publish your simulation to enable LMS package downloads."
2. Open the simulation, go to the **Publish** stage, and find **LMS Standalone**. Turn on its switch to show the options. While the switch is off, the section shows "Turn on to configure." On a plan without LMS packages, the switch is replaced by a plan badge and the options are not shown.
3. Under **Content type**, choose **Text** or **Voice**. **Voice** can only be chosen when voice is turned on for the simulation. Otherwise the section shows "Enable realtime voice for this simulation to export a voice package."
4. If the simulation has added languages, choose one under **Package language**. "Each package runs in one language. Export again for another." The control does not appear for a simulation with only its primary language.
5. Choose the format:
   - **SCORM 1.2**: "Most compatible. Choose this if you're unsure."
   - **SCORM 2004**: "Choose if your LMS specifically requires it." The package's manifest declares the schema version `2004 4th Edition`.
   - **xAPI (Tin Can)**: "Modern standard. Requires LRS-compatible LMS."
6. Select **Download Package**. The button reads "Generating..." while the zip is built.
7. Upload the zip to your LMS as you would any SCORM or xAPI package.

### The file name

The zip is named after the simulation, followed by the format (`scorm12`, `scorm2004` or `xapi`), then `-voice` for a voice package and the language code for a package in an added language. For example: `customer-call-scorm12.zip` or `customer-call-xapi-voice-es.zip`.

The name part is the simulation's name in lowercase, with spaces turned into hyphens and every character other than the letters a to z, digits and hyphens removed, cut to at most 60 characters. If nothing is left after that, the name part is `simulation`.

## What is in the package

Every package holds three files:

- `index.html`, a launcher page that fills the LMS window with the live simulation from devlin.ai.
- `lms-bridge.js`, the script that talks to the LMS.
- A manifest: `imsmanifest.xml` for the SCORM formats, `tincan.xml` for xAPI.

The simulation itself is not in the zip. It still runs on devlin.ai, and the package only handles communication with the LMS. This has three consequences:

- Learners need an internet connection. If the simulation has not loaded after a short wait, the launcher shows "This simulation requires an internet connection." and "Please check your connection and reload the page." A coach package shows "This coach requires an internet connection." with the same second line.
- The simulation must stay published. The package loads the simulation's live link, and an unpublished simulation does not load from it.
- Edits reach learners without a new upload. The package points at the simulation, so the learner always gets the current published version. You only need to download again to change the format, the content type or the package language.

A voice package's launcher grants the simulation the microphone. A text package's launcher does not.

The package only accepts results from the devlin.ai address it was downloaded from.

## What a simulation package reports

The package reports when the simulation's result is ready:

- With evaluation on, the result is sent when the evaluation finishes, not when the conversation ends. A learner who closes the package while results are still loading has not been reported yet.
- With evaluation off, the result is sent as soon as the simulation ends.

The result carries four things:

- Completion.
- Pass or fail, taken from the simulation's evaluation. The package does not have a passing score of its own.
- Score: the evaluation's percentage rounded to a whole number.
- Session time, in whole seconds. For a text simulation it runs from when the simulation loads to when the result is sent. For a voice simulation it runs from when the voice session starts.

These are the only values a SCORM package writes. It does not report individual turns, the transcript, or results per criterion. Those stay in devlin.ai: see [Criteria breakdown and attempt limits](https://docs.devlin.ai/results-and-analytics/learner-results-and-attempts).

### SCORM 1.2

| When | Field | Value |
|---|---|---|
| Package opens | `cmi.core.lesson_status` | `incomplete` |
| Result | `cmi.core.session_time` | The session time as `HH:MM:SS` |
| Result, if there is a score | `cmi.core.score.raw` | The score |
| Result, if there is a score | `cmi.core.score.min` and `cmi.core.score.max` | `0` and `100` |
| Result | `cmi.core.lesson_status` | `passed`, `failed`, or `completed` when there is no pass or fail |

SCORM 1.2 has a single status field, so a failed attempt shows as `failed`, not as `completed`.

### SCORM 2004

| When | Field | Value |
|---|---|---|
| Package opens | `cmi.completion_status` | `incomplete` |
| Result | `cmi.session_time` | The session time as a duration, for example `PT2M5S` |
| Result, if there is a score | `cmi.score.raw` | The score |
| Result, if there is a score | `cmi.score.min` and `cmi.score.max` | `0` and `100` |
| Result, if there is a score | `cmi.score.scaled` | The score as a fraction of the maximum, for example `0.85` |
| Result | `cmi.completion_status` | `completed` |
| Result, if there is a pass or fail | `cmi.success_status` | `passed` or `failed` |

When there is no pass or fail, `cmi.success_status` is left unset.

### After the result

After writing a result, a SCORM package saves it. A pass ends the LMS session at once. After any other result the session stays open, so a retried attempt can be recorded, and ends when the learner leaves the page. If the learner leaves before any result, the status stays `incomplete`.

If the learner uses Try Again without relaunching the package, the later attempt is written to the LMS too, with one rule: a pass is never replaced. After a pass, a later fail, or a later result with no pass or fail, is not written, and the passing score stays. Otherwise the latest result stands, so a fail followed by a pass is recorded as passed with the passing score.

The first time the package opens it sets the status to `incomplete` before anything else. When the LMS already holds a pass, a fail or a completion for that learner, reopening the package leaves the stored status as it is.

Packages downloaded before this change keep the earlier behavior: one result per launch, and the status reset to `incomplete` each time the package opens. Download the package again and replace it in your LMS to get the behavior described here.

### xAPI

An xAPI package sends statements to the learning record store that your LMS names when it launches the package. The LMS passes the store's address, the credentials, the learner and the registration in the launch link, which is the standard Tin Can launch. There is nothing to configure in devlin.ai. If the launch link names no store, or names no learner, no statements are sent: a result is never filed under a placeholder learner.

The package sends up to three statements, each about the simulation as an activity named after it:

1. **initialized**, when the simulation has loaded.
2. **passed**, **failed** or **completed**, when the result is ready. **completed** is used when there is no pass or fail. This statement's result holds completion (`true`), success when there is a pass or fail, the score when there is one (raw, a minimum of `0`, a maximum of `100`, and the scaled fraction), and the session time as a duration. It also holds the evaluation's written feedback as the response.
3. **terminated**, straight after the result statement.

Like the SCORM formats, an xAPI package keeps listening after the first result, so a later attempt in the same launch sends another result statement. Unlike them, it sends every result, including a fail after a pass.

## Simulations without a score

A package still reports completion when there is no score to report:

- Evaluation off. The package reports completion and the session time. There is no score and no pass or fail.
- Feedback-only mode. The evaluation runs, but it produces no score and no pass or fail, so the package reports completion only: `completed` in SCORM 1.2, completion without a success status in SCORM 2004, and a **completed** statement in xAPI.
- The evaluation could not be completed. The package reports the same way: completion without a score.

In all three cases, set your LMS to track the item by completion, not by a passing score. See [Evaluation and scoring](https://docs.devlin.ai/simulations/evaluation-and-scoring) for the scoring modes.

## Coach packages

A coach has no score, so its package works differently. Open the coach, go to the **Publish** stage, and find **LMS package**. Its package marks the course complete and passed as soon as the learner opens it, and reports no score.

The coach must be published first, and it must stay published for the package to load it. Choose **SCORM 1.2**, **SCORM 2004** or **xAPI (Tin Can)** and select **Download Package**. There is no content type or language choice. The zip is named after the coach and the format, for example `onboarding-coach-scorm2004.zip`. The name part follows the same rules as a simulation package; if nothing is left, it is `coach`.

What the coach package reports, as soon as it opens:

- **SCORM 1.2**: `cmi.core.lesson_status` is set to `passed`.
- **SCORM 2004**: `cmi.completion_status` is set to `completed` and `cmi.success_status` to `passed`.
- **xAPI**: a **passed** statement with success and completion both `true`, then a **terminated** statement.

No score and no session time are reported, and nothing the learner does in the coach changes the result.

## Reporting from a Storyline course

When a simulation sits inside a Storyline course as a Web Object, the course is what the LMS launches, and the simulation's result has to be written into the course's SCORM session. `sendResultsToLMS()` does that. It is part of the devlin.ai bridge script that the course already loads for variables: see [Embedding a simulation in Articulate Storyline](https://docs.devlin.ai/publish-and-embed/storyline-web-object-setup).

The setup is in the simulation's **Publish** stage, in the **Storyline 360** section, under **Step 5: Report Results to the LMS**. Add an Execute JavaScript trigger containing:

```
sendResultsToLMS()
```

### When to call it

Run the trigger "only after results have finished loading". The usual condition is the simulation's evaluation-complete variable (`Sim_EvalComplete` by default) becoming true. If the trigger runs before the simulation has produced a result, nothing is sent and the browser console shows "[devlin.ai] sendResultsToLMS: No sim results received yet. Make sure the sim has completed before calling this."

### What it sends

The function finds the SCORM session the course already opened. It looks for SCORM 2004 first and then SCORM 1.2, and writes the same fields as the tables above: the session time, the score with its minimum and maximum (and the scaled score in SCORM 2004), and the status. It writes to the existing session and does not open one of its own. It does not send xAPI statements, so a course published for xAPI reports nothing through it.

If the course contains more than one simulation, the function combines every result it has received on that page:

- Score: the average of the simulations' scores, rounded to a whole number.
- Pass or fail: passed only when every simulation that has a pass or fail passed.
- Session time: the simulations' times added together.

A simulation that was run again counts once, with its latest result.

The function leaves the SCORM session open, so the course keeps control of it. To also close the session from the same call, use `sendResultsToLMS({ finish: true })`.

### What it returns

The function returns an object that a developer can check, and writes a line to the browser console:

| Return value | Meaning |
|---|---|
| `{ success: true }` | The result was written to the LMS. |
| `reason: "no_results"` | No simulation on the page has finished yet. |
| `reason: "plan_not_eligible"` | The simulation's workspace is on a plan without LMS reporting. |
| `reason: "no_scorm_api"` | There is no SCORM session, for example in a Storyline preview or a web-only publish. |
| `reason: "api_error"` | The LMS raised an error while the result was being written. |

### Keep Storyline from overwriting the result

The same step in the Publish stage explains two things to set up in Storyline:

- **Prevent Storyline from overwriting your results**: Storyline reports its own completion when the learner reaches the end of the course, which can replace the score and status the simulation sent. The step walks through adding a slide learners cannot reach and setting **Tracking** to **100% of all slides in your project**, so Storyline never reports completion by itself.
- **Closing the course (optional)**: if an Exit button both runs `sendResultsToLMS()` and has an **Exit course** trigger, put the Execute JavaScript trigger above the Exit course trigger so the result is sent before the course closes.

On a plan without LMS reporting, the step shows a plan badge. You can add the trigger, but nothing is sent to the LMS until the simulation's workspace is on one of these plans: Core, Team, Enterprise.

## Related

- [Publishing a simulation and sharing its link](https://docs.devlin.ai/publish-and-embed/publish-a-simulation)
- [Storyline variables a simulation or coach writes](https://docs.devlin.ai/publish-and-embed/storyline-variables)
- [Embedding a simulation in Articulate Storyline](https://docs.devlin.ai/publish-and-embed/storyline-web-object-setup)
- [Hosting and SCORM Wrap](https://docs.devlin.ai/publish-and-embed/hosting-and-scorm-wrap)
- [Evaluation and scoring](https://docs.devlin.ai/simulations/evaluation-and-scoring)
- [Criteria breakdown and attempt limits](https://docs.devlin.ai/results-and-analytics/learner-results-and-attempts)
- [Plan availability](https://docs.devlin.ai/reference/plan-availability)
