Chapitre 6.5 — Le LRS
⏱️ TL;DR — Le LRS (Learning Record Store) est le magasin des statements : il les reçoit et il les sert, via une API REST standardisée. Quatre ressources à connaître :
statements(envoyer enPOST/PUT, relire enGET),activities/state(la mémoire clé-valeur d’une session),activities/profileetagents/profile(des données rattachées à une activité ou à une personne), etabout(ce que le LRS supporte). Tout échange porte l’en-tête obligatoireX-Experience-API-Versionet une authentification Basic ou OAuth. Deux principes structurants : les statements sont immuables (on n’édite ni ne supprime — un statement est un fait enregistré une fois pour toutes) et on annule une erreur par le voiding (un nouveau statement au verbevoidedqui pointe l’ancien). Exemples de LRS : Learning Locker, SQL LRS, Veracity, SCORM Cloud. Et surtout : un LRS n’est pas un LMS (rappel Partie 1).
🎯 Objectifs
- Décrire les quatre ressources de l’API xAPI :
statements,state,profile(activité/agent),about. - Choisir la bonne méthode HTTP :
POSTvsPUTpour envoyer,GETpour relire et filtrer. - Poser l’en-tête
X-Experience-API-Versionet l’authentification (Basic/OAuth) sur chaque appel. - Comprendre l’immutabilité des statements et savoir annuler proprement par le voiding.
- Distinguer sans hésiter un LRS d’un LMS, et citer des LRS du marché.
Ce qu’est un LRS
Un LRS fait deux choses, et deux seulement :
- il reçoit des statements (de l’appli mobile, du module web, de la tablette du formateur, d’un simulateur…) et les stocke ;
- il sert ces statements sur demande (à un tableau de bord, un outil d’analyse, une IA, un autre système).
C’est un magasin de données d’apprentissage avec une API standard : n’importe quelle source qui parle HTTP peut y écrire, n’importe quel consommateur autorisé peut y lire. Rien de plus, rien de moins — et c’est précisément ce découplage qui fait la puissance de xAPI (chapitre 6.1).
L’API REST : quatre ressources
L’API xAPI expose quelques ressources bien définies. Voici la carte :
| Ressource | Rôle | Méthodes clés |
|---|---|---|
statements | Le cœur : envoyer et relire des statements. | POST, PUT, GET |
activities/state | La mémoire de session : données clé-valeur liées à un acteur + une activité + une registration (ex. le signet, l’état de reprise). | PUT, POST, GET, DELETE |
activities/profile | Données rattachées à une activité (indépendantes d’un apprenant). | PUT, POST, GET, DELETE |
agents/profile | Données rattachées à une personne (préférences, profil). | PUT, POST, GET, DELETE |
about | Ce que le LRS supporte (versions de la spec, extensions). | GET |
Les trois ressources « Document » (state, activities/profile, agents/profile) stockent des documents libres (souvent du JSON), à toi de définir les clés. La state est particulièrement importante : c’est elle que cmi5 utilise pour transmettre les données de lancement (LMS.LaunchData) — on y reviendra en Partie 8.
💡 Réflexe — Ne confonds pas
statementsetstate. Un statement est un fait immuable (« Léa a réussi l’atelier ») : on l’ajoute, on ne le modifie jamais. Lastateest une mémoire mutable (« Léa en est à l’écran 7 ») : on l’écrase à volonté. Un fait va dansstatements; un état courant va dansstate. Mettre l’état de reprise dans des statements pollue l’historique ; mettre un fait dans lastatele rend invisible aux rapports.
Envoyer un statement : POST, PUT
Tout appel porte deux choses non négociables : l’en-tête X-Experience-API-Version (sinon le LRS rejette la requête) et une authentification (Basic ou OAuth).
POST — le LRS génère l’id du statement s’il n’est pas fourni :
POST https://lrs.formacampus.fr/xapi/statements HTTP/1.1
Content-Type: application/json
Authorization: Basic YXBwLWF0ZWxpZXI6czNjcjN0
X-Experience-API-Version: 1.0.3
{
"actor": { "mbox": "mailto:lea.martin@formacampus.fr" },
"verb": {
"id": "http://adlnet.gov/expapi/verbs/passed",
"display": { "fr-FR": "a réussi" }
},
"object": {
"id": "https://formacampus.fr/xapi/activities/atelier-soudure"
}
}PUT — tu fournis l’id (un UUID) dans l’URL. C’est idempotent : renvoyer le même PUT avec le même statementId ne crée pas de doublon. Idéal pour le mobile hors-ligne qui rejoue sa file d’attente sans savoir ce qui est déjà passé :
PUT https://lrs.formacampus.fr/xapi/statements?statementId=8f2a1c30-6b4e-4d2a-9f11-3c7e5a9b0d21 HTTP/1.1
Content-Type: application/json
Authorization: Basic YXBwLWF0ZWxpZXI6czNjcjN0
X-Experience-API-Version: 1.0.3
{
"actor": { "mbox": "mailto:lea.martin@formacampus.fr" },
"verb": { "id": "http://adlnet.gov/expapi/verbs/passed", "display": { "fr-FR": "a réussi" } },
"object": { "id": "https://formacampus.fr/xapi/activities/atelier-soudure" }
}⚠️ Piège — Oublier l’en-tête
X-Experience-API-Version. C’est la cause n°1 des « le LRS me répond 400 alors que mon JSON est bon ». Cet en-tête est obligatoire sur chaque requête xAPI ; sans lui, le LRS refuse, quel que soit le contenu. Vérifie-le en premier quand un envoi échoue. Deuxième cause : unmboxsansmailto:, ou unscaledhors-1..1(chapitres 6.3 et 6.4).
Relire les statements : GET et filtres
Le GET sur statements sert les données, avec des filtres en paramètres d’URL : par agent, par verb, par activity, par registration, par fenêtre de temps (since/until), etc.
GET https://lrs.formacampus.fr/xapi/statements?verb=http://adlnet.gov/expapi/verbs/passed&activity=https://formacampus.fr/xapi/activities/atelier-soudure HTTP/1.1
Authorization: Basic YXBwLWF0ZWxpZXI6czNjcjN0
X-Experience-API-Version: 1.0.3Le filtre agent attend un objet JSON (l’IFI de la personne), qui doit être URL-encodé dans la vraie requête :
GET https://lrs.formacampus.fr/xapi/statements?agent={"mbox":"mailto:lea.martin@formacampus.fr"} HTTP/1.1
Authorization: Basic YXBwLWF0ZWxpZXI6czNjcjN0
X-Experience-API-Version: 1.0.3Le LRS renvoie un objet StatementResult contenant un tableau statements et, si le volume l’exige, un lien de pagination (more) à suivre pour la page suivante. C’est ainsi qu’un tableau de bord reconstruit « tout le parcours de Léa » ou « tous les passed de cet atelier ce mois-ci ».
Immutabilité : un statement est gravé
Principe fondateur : un statement, une fois accepté, ne se modifie pas et ne se supprime pas. Le LRS est un journal en ajout seul (append-only). C’est voulu : un enregistrement d’apprentissage est une preuve (certification, conformité, audit). S’il pouvait être réécrit après coup, il ne prouverait plus rien.
Conséquences pratiques :
- Pas de
PUTpour « corriger » un statement existant :PUTsert à créer avec unidchoisi, pas à éditer. - Une erreur (mauvais score, mauvais apprenant) ne s’efface pas : on l’annule par le voiding, puis on réémet un statement correct.
- Un
idde statement est unique et définitif : rejouer le mêmeid(viaPUT) ne réécrit rien, c’est simplement idempotent.
⚠️ Piège — Chercher une « route de suppression/modification » de statement pour corriger une bourde. Elle n’existe pas, par conception. On ne réécrit pas l’histoire ; on la corrige en ajoutant un statement d’annulation (voiding) et un statement correct. Un LRS qui laisserait éditer un statement enregistré ne serait pas conforme.
Le voiding : annuler proprement
Pour annuler un statement erroné, on émet un nouveau statement dont :
- le verbe est le verbe réservé
http://adlnet.gov/expapi/verbs/voided(défini par la spec xAPI pour exactement cet usage) ; - l’objet est un
StatementRef(chapitre 6.3) pointant l’iddu statement à annuler.
{
"actor": {
"objectType": "Agent",
"account": { "homePage": "https://lrs.formacampus.fr", "name": "app-atelier-mobile" }
},
"verb": {
"id": "http://adlnet.gov/expapi/verbs/voided",
"display": { "en-US": "voided" }
},
"object": {
"objectType": "StatementRef",
"id": "8f2a1c30-6b4e-4d2a-9f11-3c7e5a9b0d21"
}
}Le statement d’origine reste dans le LRS (rien n’est effacé, immutabilité oblige), mais il est marqué annulé : il est exclu des requêtes ordinaires. On peut ensuite émettre le statement corrigé. Le voiding est ainsi la version « traçable et honnête » de la correction : on voit **qu’**une erreur a été annulée, par qui et quand — l’audit reste intact.
💡 Réflexe — L’acteur d’un statement de voiding est en général le système ou l’autorité qui corrige (un compte applicatif, un administrateur), pas l’apprenant. C’est une opération d’administration de la donnée, pas une expérience d’apprentissage de Léa. Garde l’apprenant hors de ses propres statements d’annulation.
LRS n’est pas LMS (rappel Partie 1)
La confusion la plus fréquente des débutants. On l’a posée en Partie 1 (anatomie de l’écosystème) ; on la reprécise, car elle décide de l’architecture :
| LMS | LRS | |
|---|---|---|
| Rôle | Orchestrer l’apprentissage | Stocker les traces d’apprentissage |
| Gère | Utilisateurs, inscriptions, cours, parcours, séquencement | Des statements (et des documents state/profile) |
| Délivre du contenu ? | Oui (lance les modules, affiche les cours) | Non |
| Interface | Une plateforme pour apprenants et formateurs | Une API REST, sans interface d’apprentissage |
| Exemple | Moodle, Canvas | Learning Locker, SQL LRS |
Un LMS peut embarquer un LRS (Moodle sait envoyer du xAPI vers un LRS via le plugin Logstore xAPI, rappel Partie 1), mais ce sont deux rôles distincts. Un LRS seul n’orchestre rien : il ne sait pas inscrire un élève, ni lui présenter un cours, ni gérer un parcours. Il encaisse et restitue des faits. Inversement, un LMS classique sans LRS ne sait pas stocker des expériences venues de l’extérieur (mobile, présentiel). Les deux sont complémentaires.
Quelques LRS du marché (à nommer, pas à vendre) : Learning Locker, SQL LRS (Yet Analytics), Veracity, et SCORM Cloud (Rustici), qui fait aussi office de bac à sable pour tester SCORM / xAPI / cmi5 / LTI.
🔌 Côté intégration — Quand un client demande « brancher notre appli sur votre LRS » (ou l’inverse, « envoyer vers le nôtre »), ce qu’il vérifie, c’est : (1) que tu émets des statements conformes vers le bon endpoint
statements, (2) avec l’en-tête de version et l’auth attendus, et (3) des verbes/activités cohérents (mêmes IRI d’un envoi à l’autre). Un LRS interchangeable est un argument commercial : l’école n’est pas enfermée, elle peut pointer ses données vers le LRS de son choix. C’est l’exact opposé du « contenu prisonnier » de la Partie 1.
📚 La spec — Les ressources de l’API (
statements,activities/state,activities/profile,agents/profile,about), les méthodes, l’en-tête obligatoireX-Experience-API-Version, l’authentification (Basic/OAuth), l’immutabilité des statements et le mécanisme de voiding (verbe réservéhttp://adlnet.gov/expapi/verbs/voided+ objetStatementRef) sont définis par xAPI (ADL,adlnet.gov; IEEE 9274.1.1 pour la 2.0). Pour la mise en œuvre (auth, envoyer/lire en pratique, hors-LMS), voir la Partie 7.
🧭 Sur FormaCampus — Le premier LRS branché. FormaCampus se dote d’un LRS (au départ un Learning Locker de test, avant un déploiement plus costaud) sur
https://lrs.formacampus.fr/xapi. L’appli tablette des formateurs yPOSTles statements d’atelier ; l’appli mobile de révision, elle, utilisePUTavec unstatementIdgénéré côté client — comme ça, quand elle rejoue sa file d’attente au retour du réseau, aucun doublon. Un après-midi, un formateur valide par erreur le geste de soudure au mauvais apprenant : pas de panique, on ne bricole pas la base. On émet un statementvoidedpointant l’idfautif (l’audit garde la trace de l’erreur et de sa correction), puis le bon statement. Enfin, le tableau de bord de l’école interroge le LRS enGET— « tous lespasseddu parcours métallerie ce trimestre » — et FormaCampus voit enfin l’apprentissage présentiel remonter, à côté du web et du mobile. Le LRS n’a pas remplacé le LMS de l’école : il l’a complété pour tout ce qui se passe hors de ses murs.
✏️ Exercices
Exercice 1 — POST ou PUT ? Ton appli mobile stocke les statements hors-ligne et les rejoue quand le réseau revient. Quelle méthode choisis-tu pour l’envoi, et pourquoi ? Quel risque évite-t-elle ?
✅ Solution
PUT avec un statementId (UUID) généré côté client au moment où l’expérience a lieu. Le PUT est idempotent : si la file d’attente rejoue deux fois le même statement (crash, double synchro, réseau instable), le LRS voit le même statementId et ne crée pas de doublon. Avec un POST (le LRS génère l’id), le même statement rejoué deviendrait deux enregistrements distincts — score compté en double, historique faussé. Sur du mobile hors-ligne, PUT idempotent est le bon réflexe.
Exercice 2 — Annuler sans effacer. Un statement (id a1b2c3d4-…) attribue à tort un passed à Léa alors que c’était Sami. Décris la procédure correcte, et dis ce qui est interdit.
✅ Solution
Interdit : modifier ou supprimer le statement fautif — les statements sont immuables, il n’existe aucune route pour ça. Procédure correcte : (1) émettre un statement de voiding — verbe http://adlnet.gov/expapi/verbs/voided, objet StatementRef pointant a1b2c3d4-…, acteur = le système/administrateur ; le statement d’origine reste stocké mais est marqué annulé et exclu des requêtes ordinaires. (2) Émettre le statement correct (le passed de Sami). L’audit garde ainsi la trace de l’erreur et de sa correction : on corrige sans réécrire l’histoire.
Exercice 3 — LRS ou LMS ? Pour chaque besoin, dis si c’est le rôle d’un LRS ou d’un LMS : (a) inscrire une classe à un cours et lui présenter les modules dans l’ordre ; (b) recevoir les statements de l’appli mobile et de l’atelier présentiel ; (c) afficher un tableau de bord agrégeant « qui a réussi quoi, partout ».
✅ Solution
(a) LMS : inscriptions, présentation et séquencement des cours = orchestration de l’apprentissage. (b) LRS : encaisser des statements venus de sources variées, y compris hors du LMS (mobile, présentiel), c’est exactement son rôle. (c) LRS pour la source de données (on l’interroge en GET), le tableau de bord lui-même étant un outil de restitution qui consomme le LRS. Le fil conducteur : le LMS orchestre et délivre ; le LRS stocke et sert des traces. Ils se complètent, ils ne se remplacent pas.
🧠 Quiz de révision
1. Cite les ressources principales de l’API xAPI d’un LRS.
statements (envoyer/relire les statements, POST/PUT/GET), activities/state (mémoire de session clé-valeur), activities/profile et agents/profile (documents rattachés à une activité ou à une personne), et about (versions et capacités supportées par le LRS).
2. Quel en-tête est obligatoire sur chaque requête, et que se passe-t-il s’il manque ?
X-Experience-API-Version. S’il manque, le LRS rejette la requête (typiquement un 400), même si le JSON est parfait. C’est la première chose à vérifier quand un envoi échoue.
3. Quelle différence entre POST et PUT pour envoyer un statement ?
POST et PUT pour envoyer un statement ?POST : le LRS génère l’id du statement. PUT : tu fournis l’id (statementId dans l’URL) et l’opération est idempotente — rejouer le même PUT ne crée pas de doublon. PUT est idéal pour le mobile hors-ligne qui rejoue sa file d’attente.
4. Que signifie l’immutabilité des statements, et comment corriger une erreur ?
Un statement enregistré ne peut être ni modifié ni supprimé : le LRS est un journal en ajout seul (preuve/audit). On corrige une erreur par le voiding — un nouveau statement au verbe http://adlnet.gov/expapi/verbs/voided dont l’objet est un StatementRef vers le statement fautif — puis on réémet le statement correct. Rien n’est effacé.
5. En une phrase, la différence entre un LRS et un LMS ?
Un LMS orchestre et délivre l’apprentissage (utilisateurs, inscriptions, cours, séquencement) ; un LRS stocke et sert les traces d’apprentissage (statements) via une API REST, y compris celles venues hors du LMS. Un LMS peut embarquer un LRS, mais ce sont deux rôles distincts et complémentaires.
Partie suivante : Partie 7 — xAPI en pratique — on passe à l’action : authentification, envoyer et lire pour de vrai, la State API, et tracer hors-LMS (mobile, présentiel).