How to Write Good xAPI Statements – Verbs, Objects and Context

xAPI lets you record almost anything. That's the gift and the trap. Six months after launch, someone opens the Learning Record Store and finds "completed," "finished," "done" and "complete" all meaning the same thing, activity IDs that changed with every course update, and a learner who appears under three different email addresses. Nobody can build a report from it. The fix isn't more data; it's better xAPI statements from the start. How do you write statements that stay clean, consistent and useful?
Good xAPI statements are consistent, specific and designed backward from the questions you'll need to answer. Use established verbs from the ADL vocabulary or a published profile instead of inventing synonyms. Give every activity a stable, unique identifier you control, and identify learners the same way every time. Put the score, success, completion and duration in the result, and use context to link each statement to its course, registration and parent activity. Before you build anything, write the report you want, then design the statements that feed it.
What Makes Up an xAPI Statement?
An xAPI statement is a small JSON record built around actor, verb and object, with optional result, context and timestamp. Think of it as a sentence with a few footnotes. "Alex answered question 3 correctly in the hazard communication quiz in about 40 seconds" becomes:
{
"actor": { "account": { "homePage": "https://lms.example.com", "name": "E10423" } },
"verb": { "id": "http://adlnet.gov/expapi/verbs/answered",
"display": { "en-US": "answered" } },
"object": {
"id": "https://training.example.com/xapi/hazcom/quiz/q3",
"definition": {
"name": { "en-US": "Hazcom quiz, question 3" },
"type": "http://adlnet.gov/expapi/activities/cmi.interaction",
"interactionType": "choice"
}
},
"result": { "success": true, "response": "b", "duration": "PT40S" },
"context": {
"registration": "1b9c2f6e-5d7a-4c11-9d1e-0f2c7a8b3e44",
"contextActivities": {
"parent": [ { "id": "https://training.example.com/xapi/hazcom/quiz" } ],
"grouping": [ { "id": "https://training.example.com/xapi/hazcom" } ]
}
},
"timestamp": "2017-03-14T15:22:09Z"
}
Actor is who, verb is what happened, object is to what, result is how it went, and context is where it fits. The official xAPI specification defines every property if you need the exact rules.
How Do You Choose Good Verbs?
Choose verbs that already exist and mean exactly what happened. Verbs in xAPI are identified by web addresses (IRIs), not by their display words, so "completed" from your course and "completed" from someone else's only match if they use the same IRI.
Start with the ADL verb list, which covers the common cases, and check whether a published profile exists for your activity type before inventing anything. Here are verbs that cover a lot of training scenarios:
| Verb | Use it when |
|---|---|
| experienced | The learner viewed or encountered content, such as a page or slide |
| attempted | The learner started an activity that has a result, such as a quiz |
| answered | The learner responded to a question |
| completed | The learner finished everything required in an activity |
| passed / failed | The learner met, or didn't meet, a defined standard |
| progressed | The learner moved partway through, often with a progress measure |
| interacted | The learner used an element without a scored outcome |
| commented | The learner left feedback or a note |
Two rules keep verbs clean. First, one meaning per verb across your whole program. Second, don't encode the result in the verb. "Answered" plus a result of success true is better than inventing "answered-correctly," because reports can filter on success without knowing your private vocabulary.
How Should You Identify Activities?
Give every activity an IRI that is unique, stable and under a domain you control. That last part matters: if you use a vendor's domain or a temporary URL, you lose control of your identifiers when things change.
- Keep IDs stable across versions. Fixing a typo in a course shouldn't create a brand-new activity. If the content changes meaning, then create a new ID on purpose.
- Use a readable, hierarchical pattern, such as
/xapi/course/module/item. It makes debugging and reporting much easier. - Include a definition with a human-readable name and an activity type, so reports display something better than a long URL.
- For questions, set the interaction type (choice, true-false, fill-in, matching, sequencing, numeric, likert and so on) and the correct response pattern when it applies.
How Should You Identify the Learner?
Identify the learner the same way everywhere, using an identifier that won't change. xAPI lets you use an email address (mbox), a hashed email, or an account made of a home page and a user ID. Emails change when people get married, change names or switch domains, so an account using a stable employee or LMS ID is usually the safer choice.
Agree on this across every system that sends statements. If your LMS sends one identifier and your mobile app sends another, the same person becomes two people in your data, and no reporting tool can safely merge them later. Also consider privacy: send only what you need to identify the learner, and avoid stuffing names or personal details into extensions.
What Belongs in Context and Result?
Result holds the outcome. Context holds the surroundings. Mixing them up is one of the most common design mistakes.

Result
Use the result for score (scaled from -1 to 1, plus raw, min and max if you like), success, completion, the learner's response and duration in ISO 8601 format (for example, PT40S for 40 seconds). Only include properties you actually know. A statement saying "experienced" doesn't need a score.
Context
Use context to place the statement in its bigger picture:
- Registration: an ID that ties every statement from one enrollment or attempt together
- Parent: the activity directly above this one, such as the quiz a question belongs to
- Grouping: broader containers, such as the course or program
- Category: the profile the statement follows
- Instructor, team, platform and language, when they matter to your reports
- Extensions, for anything else, with IRIs as keys and a documented meaning
The parent and grouping links are what let you ask "how did people do on this quiz across all three courses that include it?" Without them, each question floats alone.
Which Statement Habits Ruin Your Reports?
Most problems come from skipping the planning step. These are the habits I look for first in any review:
| Mistake | Why it hurts | Better approach |
|---|---|---|
| Made-up verbs for common actions | Reports can't match them with other tools | Use ADL or profile verbs |
| Activity IDs that change on republish | History splits into unconnected pieces | Fixed IDs under your own domain |
| Different learner IDs per system | One person looks like several | One agreed account identifier |
| No registration or parent context | Can't group attempts or roll up results | Always send registration and parent |
| A statement for every mouse click | Noise drowns the signal and costs storage | Track events that answer a question |
How Do You Test xAPI Statements Before Launch?
Test statements the same way you'd proofread a document: send them, read them back, and have a second person check them against the plan. A sandbox LRS is ideal for this, and many LRS products offer a free or trial environment.
- Run through the activity as a learner and send every statement type at least once, including failures and exits.
- Check that the LRS accepted every statement. A rejected statement usually means a formatting error, such as a missing verb ID or an invalid duration.
- Query statements back by learner, by activity and by registration. If a query that should work returns nothing, an identifier is inconsistent.
- Compare every verb, ID and extension against your data dictionary. Small spelling differences are the most common problem.
- Build one draft report from the test data and confirm it answers its question.
Repeat a quick version of this test whenever the course or app is updated. Statements tend to drift over time as different developers touch the code, and a ten-minute check keeps your data consistent for years.
How Do You Design Statements from the Report Backward?
Start with the report, then design the data. Write down the questions managers and designers will ask, sketch the tables or charts that answer them, and list exactly which statements, with which properties, those charts need. Only then build.
- Write three to five questions, such as "Which scenario choices do new supervisors get wrong most often?"
- Sketch the report for each question.
- List the statements needed: verb, activity, result and context for each.
- Write a one-page data dictionary: verbs, ID patterns, learner identifier, extensions.
- Send test statements to a sandbox LRS and build the report from them before launch.
This is the same discipline that makes any report trustworthy. If you're deciding which numbers deserve a place on a dashboard at all, my guide to LMS reporting covers what's worth tracking and why.
Good xAPI Statements Are Written for the Reader, Not the Writer
Writing xAPI statements is a lot like writing for a person: clear words, consistent meaning, and enough context to understand what happened later. Use shared verbs, stable activity IDs and one learner identifier. Put outcomes in the result and relationships in the context. Design from the report backward.
Before the next xAPI project kicks off, agree on your verbs, ID patterns and learner identifier, and write them down where every developer will see them. It takes about an hour, and it's the difference between a Learning Record Store full of answers and one full of noise.