xAPI statement structure, field by field

The xAPI statement structure explained field by field: actor, verb, object, result and context, with a worked example and the rules an LRS rejects.

What is the structure of an xAPI statement?

An xAPI statement is a JSON object with three required fields — actor, verb, object — plus optional result, context, timestamp and id. The xAPI statement structure reads as a sentence: someone did something to something. Everything else qualifies that sentence with a score, a duration, or the attempt it belonged to.

That is the whole model, and it is why xAPI can describe a quiz, a video, a simulator and a classroom sign-in with the same shape. The difficulty is never the shape: several fields carry identifiers that two systems must agree on exactly, and a report that looks empty is usually a disagreement about one of them rather than missing data. A complete statement, with every field this article covers:

{
  "id": "9f6b1a34-7c02-4e58-9d21-1b0a7e4d55c9",
  "actor": {
    "objectType": "Agent",
    "name": "Dana Whitfield",
    "account": {
      "homePage": "https://lms.example.org",
      "name": "u-40219"
    }
  },
  "verb": {
    "id": "http://adlnet.gov/expapi/verbs/passed",
    "display": { "en-GB": "passed" }
  },
  "object": {
    "objectType": "Activity",
    "id": "https://example.org/courses/fire-safety/final-assessment",
    "definition": {
      "name": { "en-GB": "Final assessment" },
      "type": "http://adlnet.gov/expapi/activities/assessment"
    }
  },
  "result": {
    "score": { "scaled": 0.84, "raw": 21, "min": 0, "max": 25 },
    "success": true,
    "completion": true,
    "duration": "PT4M12S"
  },
  "context": {
    "registration": "6c1e8f0a-2d4b-4b7e-9a3f-0f5b1c2d3e4f",
    "platform": "Example LMS",
    "language": "en-GB",
    "contextActivities": {
      "parent": [ { "id": "https://example.org/courses/fire-safety" } ]
    }
  },
  "timestamp": "2026-09-08T09:14:03Z"
}
FieldRequiredWhat it carries
actorYesWho did it — an Agent or a Group, carrying exactly one identifier
verbYesWhat they did, as an IRI, with an optional human-readable display
objectYesWhat they did it to — usually an Activity with a stable IRI
resultNoScore, success, completion, response and duration
contextNoRegistration, parent and grouping activities, platform, language
timestampNoWhen the event happened, as reported by the sender
idNoA UUID for the statement; the store assigns one if you omit it

Two more fields appear on statements you read back but never on ones you send: stored, the time the store received it, and authority, the credential that wrote it. If you are new to the model as a whole, what xAPI is and how it differs from SCORM covers the protocol around these statements.

Identifying the actor

The actor is an Agent (one person) or a Group, and an Agent must carry exactly one inverse functional identifier — the field that says which human this is. There are four:

  • mbox — an email address written as a mailto: IRI, such as mailto:dana@example.org. Simple, and it puts an email address in every record you ever store.
  • mbox_sha1sum — the SHA-1 hash of that mailto: IRI, for when you want matching without storing the address.
  • openid — an OpenID URI.
  • account — an object with a homePage (the system that issued the id) and a name (the id itself). This is what an LMS launch almost always sends, because the platform has a user id and no reason to expose an email.

Sending two identifiers, or none, makes the statement invalid and the store will reject it. The subtler failure is sending a valid one that nobody else uses. An Agent identified by account on https://lms.example.org and the same person identified by mbox are two different learners as far as any query is concerned, and no amount of reporting configuration will merge them after the fact. Decide the identifier once, per source system, before you send anything you intend to report on.

A Group is either anonymous — no identifier, just a member array of Agents, useful for a team that has no id in any system — or identified, with its own identifier and optionally members. Groups are rare in course data and common in classroom, cohort and team scenarios.

Verbs are IRIs

The verb has one required field, id, and it is an IRI — a URL-shaped identifier — not the English word. display is a language map for humans and carries no meaning at all to the store: two systems agree a learner passed only if both sent http://adlnet.gov/expapi/verbs/passed, whatever their display strings say.

ADL publishes the vocabulary most content uses under http://adlnet.gov/expapi/verbs/, including completed, passed, failed, attempted, experienced, answered, launched, initialized and terminated. One is reserved: voided. Stored statements are immutable, so a mistake is not edited or deleted — it is voided by a new statement whose verb is voided and whose object is a StatementRef pointing at the old statement's id.

You may mint your own verb IRIs on a domain you control, and for genuinely domain-specific actions you should. The cost is that nothing else in the world knows what they mean, so a dashboard built elsewhere will not chart them. Use the published vocabulary for anything that maps onto it, and invent only where nothing fits.

The object and its definition

Most of the time the object is an Activity: an id that is an IRI you invent, plus an optional definition. The id is the single most load-bearing string in your data. Every statement about the same quiz must use the same activity id forever; change it during a content update and your reports show two unrelated activities with the attempts split between them. Choose a scheme that does not encode anything likely to change — not the file name, not the version, not the folder it happened to sit in.

The definition describes the activity for anything that has to render it:

  • name and description — language maps, keyed by tag such as en-GB.
  • type — an IRI saying what kind of thing this is, for example http://adlnet.gov/expapi/activities/assessment or .../cmi.interaction for a single question.
  • moreInfo — a URL a human can open.
  • interactionType and its companions (correctResponsesPattern, choices, scale, source, target, steps) for question-level activities. The permitted interaction types are true-false, choice, fill-in, long-fill-in, matching, performance, sequencing, likert, numeric and other — the same list SCORM 2004 uses, which is not a coincidence.

An object does not have to be an Activity. It can be an Agent or Group ("Dana mentored Ravi"), a StatementRef pointing at another statement (how voiding and commenting work), or a SubStatement, a whole statement embedded as the object to express something that did not happen yet — "Dana was assigned to complete the fire-safety course". A SubStatement is data about an intention; it is not itself a record that the thing occurred.

Result: score, success, duration

The result object is optional and so is every field inside it. That flexibility is the trap: a score with no success, or a completion with no score, is valid, will be stored without complaint, and will then quietly fail whatever rule your report applies.

  • score.scaled — a number from −1 to 1. This is the field most reporting tools read first, and the only one comparable across activities with different point totals.
  • score.raw, score.min, score.max — the points as marked. raw should sit between min and max; nothing forces it to, so it is worth checking what your content sends.
  • success — a boolean: did they pass. Separate from completion, exactly as in SCORM 2004, where completion and success are two different fields and conflating them is a classic reporting bug.
  • completion — a boolean: did they finish. A learner can be completion: true, success: false, which is a finished, failed attempt, and that combination is the one most naive dashboards get wrong.
  • response — what the learner actually answered, as a string, formatted according to the activity's interaction type.
  • duration — ISO 8601, not seconds. PT4M12S is four minutes twelve seconds; PT1H30M is ninety minutes.
Duration is where integrations break most often. Sending 252 or "4:12" where the specification wants PT4M12S produces an invalid statement that a strict store rejects outright and a lenient one accepts and cannot aggregate. If a time-on-task report reads zero across the board, check the format before you check the query.

Context and registration

The context object answers "as part of what?", and the field that matters most is registration: a UUID identifying one attempt at one course by one learner. Every statement in that attempt — launched, answered, answered, answered, passed, terminated — carries the same registration, which is what lets a store reassemble a sequence of statements into a session. Without it you have a pile of events with timestamps and a lot of guessing about which belong together.

contextActivities places the activity in a structure, using four buckets:

  • parent — the thing this activity is directly part of. A question's parent is the assessment; the assessment's parent is the course.
  • grouping — looser membership, such as the curriculum or programme the course belongs to.
  • category — which profile or specification the statement follows. This is the mechanism a profile uses to mark its own statements so that a store can filter to exactly the ones that count; cmi5 is the widely deployed example, and it is why cmi5 data is queryable in a way that free-form xAPI often is not.
  • other — anything relevant that is none of the above.

The remaining context fields are smaller but useful: platform (a free-text name for the sending system), language (an RFC 5646 tag), revision (the content version), instructor, and extensions for anything the specification does not model, keyed by an IRI you own. platform and revision are only permitted when the object is an Activity.

Get actor, activity id and registration consistent and everything downstream is a query. Get any one of them wrong and the data is technically valid, permanently stored, and useless — which is the practical argument for testing what your content sends against a real store before a cohort runs through it, not after. What a learning record store does covers the receiving side of that test.

Where SCORM Central fits

SCORM Central converts files you already have — PDF, PowerPoint, Word, or video — into SCORM 1.2, SCORM 2004, cmi5, or xAPI, and shows you the manifest, the full runtime log, and a conformance report on every build, so you can see exactly what a package will report before an LMS does. See how it works, or start with one file.

Keep reading

How-to4 min Convert SCORM to video or PowerPoint? Can you convert SCORM to MP4, HTML5 or PowerPoint? What is inside a package, the routes that work, and what you lose with each one. Explainer4 min Does your LMS support xAPI? How to check What xAPI LMS support really means: launching xAPI content, a built-in LRS, or forwarding statements. The questions to ask and a quick test to run. Explainer4 min LMS vs LRS: what is the difference? LMS vs LRS: an LMS runs courses and learners; an LRS stores xAPI statements. Why SCORM does not use an LRS, and when you need both.

Start with one file.

Upload something you already have and see it as a package, with the conformance report and the runtime log alongside it.

→ upload  handbook.pdf
  parsing … 14 sections
  building manifest …
  validating … 0 errors, 2 notes
✓ handbook-scorm2004.zip
✓ preview link ready