Skip to Content

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 en POST/PUT, relire en GET), activities/state (la mémoire clé-valeur d’une session), activities/profile et agents/profile (des données rattachées à une activité ou à une personne), et about (ce que le LRS supporte). Tout échange porte l’en-tête obligatoire X-Experience-API-Version et 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 verbe voided qui 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 : POST vs PUT pour envoyer, GET pour relire et filtrer.
  • Poser l’en-tête X-Experience-API-Version et 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 :

  1. il reçoit des statements (de l’appli mobile, du module web, de la tablette du formateur, d’un simulateur…) et les stocke ;
  2. 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 :

RessourceRôleMéthodes clés
statementsLe cœur : envoyer et relire des statements.POST, PUT, GET
activities/stateLa 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/profileDonnées rattachées à une activité (indépendantes d’un apprenant).PUT, POST, GET, DELETE
agents/profileDonnées rattachées à une personne (préférences, profil).PUT, POST, GET, DELETE
aboutCe 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 statements et state. Un statement est un fait immuable (« Léa a réussi l’atelier ») : on l’ajoute, on ne le modifie jamais. La state est une mémoire mutable (« Léa en est à l’écran 7 ») : on l’écrase à volonté. Un fait va dans statements ; un état courant va dans state. Mettre l’état de reprise dans des statements pollue l’historique ; mettre un fait dans la state le 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" } }

PUTtu 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 : un mbox sans mailto:, ou un scaled hors -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.3

Le 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.3

Le 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 PUT pour « corriger » un statement existant : PUT sert à créer avec un id choisi, 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 id de statement est unique et définitif : rejouer le même id (via PUT) 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’id du 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 :

LMSLRS
RôleOrchestrer l’apprentissageStocker les traces d’apprentissage
GèreUtilisateurs, inscriptions, cours, parcours, séquencementDes statements (et des documents state/profile)
Délivre du contenu ?Oui (lance les modules, affiche les cours)Non
InterfaceUne plateforme pour apprenants et formateursUne API REST, sans interface d’apprentissage
ExempleMoodle, CanvasLearning 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 obligatoire X-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 + objet StatementRef) 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 y POST les statements d’atelier ; l’appli mobile de révision, elle, utilise PUT avec un statementId gé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 statement voided pointant l’id fautif (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 en GET — « tous les passed du 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 : 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).

Last updated on