What is the object in an xAPI statement?
The xAPI object is the part of a statement that says what the actor acted on. It is usually an Activity, such as a course, a module or a single question, identified by an IRI in its id field. It can also be an Agent, a Group, a StatementRef or a SubStatement.
A statement reads as a sentence: actor, verb, object. "Dana completed the fire safety module." The actor is the subject, the verb is what they did, and the object is the thing it was done to. The other two parts are covered in xAPI statement structure, field by field. This article stays with the object, because it is the field that decides how your data groups in a report.
Here is a complete statement with the object in its most common form:
{
"actor": { "mbox": "mailto:dana@example.com" },
"verb": { "id": "http://adlnet.gov/expapi/verbs/completed" },
"object": {
"objectType": "Activity",
"id": "https://example.com/courses/fire-safety/module-1",
"definition": {
"name": { "en-GB": "Fire safety: module 1" },
"type": "http://adlnet.gov/expapi/activities/module"
}
}
}
Only id is required. Everything else in the object is description.
The five xAPI object types
The objectType field says which kind of thing the object is. The specification allows five.
| objectType | What it points at | Typical use |
|---|---|---|
Activity | A piece of learning content or a task, named by an IRI | "completed the module", "answered question 4" |
Agent | One person or system | "mentored Sam", "assigned the course to Sam" |
Group | A set of Agents | "presented to the night shift team" |
StatementRef | Another statement that is already stored, by its UUID | Voiding a statement, or commenting on one |
SubStatement | A whole statement nested inside this one | "planned to complete the module" |
When objectType is left out, the object is treated as an Activity. That is why many statements you see in the wild have no objectType at all. The shortcut only works for Activities: an Agent, Group, StatementRef or SubStatement used as an object has to say what it is.
Two of the five deserve a note. A StatementRef is how voiding works. You cannot delete a statement from an LRS, so you send a new one whose verb is voided and whose object is a StatementRef carrying the id of the statement to cancel. A SubStatement is typically used for something that has not happened yet, such as a plan. It is never stored as a statement in its own right, so it has no id of its own, and it cannot contain another SubStatement.
In practice almost every statement a course sends has an Activity as its object, so the rest of this article is about Activities.
Activity IDs are IRIs
An Activity must have an id, and the id must be an IRI: an Internationalized Resource Identifier, the same shape as a URL but allowed to contain non-ASCII characters. It has to be absolute, with a scheme at the front. module-1 is not a valid activity ID. https://example.com/courses/fire-safety/module-1 is.
The IRI is a name, not a link. Nothing in the specification requires it to open a page, and many valid IDs return a 404 if you paste them into a browser. What the specification does expect is that the ID is unique and that it goes on meaning the same thing. To make that achievable, it recommends that whoever creates an ID uses a domain they control. If you mint IDs under your own domain, nobody else will pick the same one by accident, and you decide what each one means.
A workable pattern is one path per level of the course:
https://example.com/courses/fire-safety https://example.com/courses/fire-safety/module-1 https://example.com/courses/fire-safety/module-1/quiz https://example.com/courses/fire-safety/module-1/quiz/q4
Two habits cause trouble later:
- Using the address the course is hosted at. It looks natural, but the day the course moves to another server or another LMS, the address changes and so does the ID. Pick an ID that describes the activity, not where it is stored this year.
- Letting the authoring tool generate a fresh ID on each publish. If a tool does this, every republish looks like a new course to the LRS. Check whether your tool lets you set the ID by hand and keep it.
If the content is packaged as cmi5, part of this is decided for you: the LMS passes an activityId on the launch URL and the content must use it as the object id in the statements cmi5 requires. That handshake is one of the things the package adds, as set out in cmi5 vs xAPI.
Writing an activity definition
The optional definition is where the human-readable detail lives. An LRS can work without it, but a report that shows only IRIs is hard to read.
- name and description are language maps: an object whose keys are language tags and whose values are the text in that language, such as
{ "en-GB": "Fire safety", "fr": "Sécurité incendie" }. One activity can carry its name in every language the course ships in. - type is an IRI saying what kind of activity this is. ADL publishes common ones, including
http://adlnet.gov/expapi/activities/course,.../moduleand.../assessment. - Interaction details describe a question. Set
typetohttp://adlnet.gov/expapi/activities/cmi.interaction, add aninteractionTypesuch aschoice,true-falseorfill-in, and optionally acorrectResponsesPatternand the list ofchoices. - moreInfo is a URL where a person can read about the activity, and extensions holds anything else, keyed by IRI.
A multiple-choice question as an object looks like this:
"object": {
"id": "https://example.com/courses/fire-safety/module-1/quiz/q4",
"definition": {
"name": { "en-GB": "Which extinguisher is safe on electrical fires?" },
"type": "http://adlnet.gov/expapi/activities/cmi.interaction",
"interactionType": "choice",
"correctResponsesPattern": ["co2"],
"choices": [
{ "id": "water", "description": { "en-GB": "Water" } },
{ "id": "co2", "description": { "en-GB": "Carbon dioxide" } }
]
}
}
The learner's actual answer does not go here. It goes in the statement's result, in response. The definition describes the question, and it should be the same for every learner who sees it.
Why IDs must stay stable
An LRS does not know what your course is. It knows activity IDs. Every report that says "how many people completed module 1" is really asking "how many statements have this verb and this object ID". If the ID changes, the LRS sees a different activity. The old statements are still stored, but they belong to something the new ones do not, and the completion count splits in two.
http in place of https, or a capital letter in the path is enough to produce a second activity in your reports.Stability cuts both ways. The ID should not change while the activity is the same, and it should not be reused once the activity is different. A reworded sentence or a fixed typo is the same activity. A rewritten assessment with different questions and a different pass mark is not, and giving it a new ID keeps the old results from being read as results on the new test.
The definition is less binding than the ID, but it is not free text you can vary per statement. An LRS may keep one definition for each ID and is not obliged to adopt a different one that arrives later, so sending a different name with every statement gives results that depend on the LRS. Decide the definition once and send it the same way each time.
Before a course goes out, three checks are worth the time:
- Publish the course twice and compare the object IDs in the statements. They should be identical.
- Launch it from two different places and compare again. The IDs should not contain the host it was launched from.
- Write the ID scheme down, so that whoever updates the course next year keeps to it.
If statements, verbs and the LRS are still new ground, start with what xAPI is and come back to the ID scheme before you publish anything learners will see.
Where SCORM Central fits
SCORM Central turns PowerPoint, PDF, and video files into SCORM 1.2 or SCORM 2004 4th Edition packages you can edit in its course builder, and its free tester shows the manifest checks and every runtime call a package makes before an LMS sees it. See how it works, or test a package free.