Chapitre 10.3 — AGS : renvoyer des notes
⏱️ TL;DR — AGS (Assignment and Grade Services) est le service qui crée la valeur : ton outil écrit dans le carnet de notes du LMS. Le lancement te livre l’endpoint dans le claim
.../lti-ags/claim/endpoint={ "scope": [...], "lineitems": "URL", "lineitem": "URL" }. Une colonne de carnet s’appelle unlineitem; on les gère via le conteneurlineitems(GET/POST). Pour publier une note, on faitPOST {lineitem}/scoresavec un objet Score (userId,scoreGiven,scoreMaximum,activityProgress,gradingProgress,timestamp,comment). Pour relire les notes,GET {lineitem}/results. Trois scopes :.../lti-ags/scope/lineitem(gérer les colonnes),.../lti-ags/scope/score(publier),.../lti-ags/scope/result.readonly(lire). Ce chapitre te fait remonter une note de bout en bout.
🎯 Objectifs
- Lire le claim endpoint AGS et distinguer
lineitems(conteneur) delineitem(colonne). - Publier un score :
POST {lineitem}/scoresavec un objet Score complet et correct. - Créer une colonne (
lineitem) quand elle n’existe pas encore. - Relire les résultats via
GET {lineitem}/results. - Comprendre
activityProgress/gradingProgresset pourquoi ils comptent.
Le claim endpoint AGS
Tout part du claim déjà croisé au chapitre 10.1, présent dans le id_token du lancement :
{
"https://purl.imsglobal.org/spec/lti-ags/claim/endpoint": {
"scope": [
"https://purl.imsglobal.org/spec/lti-ags/scope/lineitem",
"https://purl.imsglobal.org/spec/lti-ags/scope/result.readonly",
"https://purl.imsglobal.org/spec/lti-ags/scope/score"
],
"lineitems": "https://moodle.ecole.fr/mod/lti/services.php/2/lineitems",
"lineitem": "https://moodle.ecole.fr/mod/lti/services.php/2/lineitems/5/lineitem"
}
}Deux URLs, deux granularités :
lineitems(pluriel) : le conteneur de toutes les colonnes du cours.GETpour lister,POSTpour créer une colonne.lineitem(singulier) : la colonne déjà liée à cette activité précise. Présente quand l’activité a été créée avec une colonne (souvent via lelineItemdu Deep Linking, chapitre 10.2). C’est sur elle que tu postes le score.
Le champ scope liste ce que cette plateforme t’autorise. Tu demanderas ces scopes lors de l’obtention du jeton d’accès.
Cas nominal : publier un score
Ton élève finit l’exercice avec 17/20. Tu as déjà l’URL lineitem (persistée au lancement) et un access_token de scope .../lti-ags/scope/score (chapitre 10.1). Tu postes sur {lineitem}/scores :
POST /mod/lti/services.php/2/lineitems/5/lineitem/scores HTTP/1.1
Host: moodle.ecole.fr
Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9...
Content-Type: application/vnd.ims.lis.v1.score+json
{
"userId": "u-2342",
"scoreGiven": 17,
"scoreMaximum": 20,
"activityProgress": "Completed",
"gradingProgress": "FullyGraded",
"timestamp": "2026-05-12T14:32:05.000Z",
"comment": "Bravo. Revois les fractions equivalentes (question 7)."
}Champ par champ :
userId: l’identifiant de l’élève chez la plateforme — c’est lesubdu lancement (chapitre 9.5), pas l’e-mail. C’est la clé qui range la note en face du bon élève.scoreGiven: la note obtenue. Si tu l’envoies,scoreMaximumdevient obligatoire.scoreMaximum: le barème (ici 20). La plateforme normalisescoreGivensur ce maximum ; ne l’omets jamais quand tu envoies unscoreGiven.activityProgress: où en est l’élève dans l’activité — valeursInitialized,Started,InProgress,Submitted,Completed.gradingProgress: où en est la correction — valeursNotReady,Failed,Pending,PendingManual,FullyGraded.timestamp: quand l’événement s’est produit, au format ISO 8601. Il ordonne les scores : un score plus récent l’emporte ; untimestampantérieur au dernier reçu peut être ignoré.comment: un retour textuel, affiché à l’élève dans le carnet (optionnel).
⚠️ Piège — Publier un
scoreGivensansscoreMaximum. La note remonte alors « brute » et la plateforme ne sait pas la rapporter au barème : 17 sur… combien ? Résultat courant : une note incohérente (17/100, ou rejet). Règle :scoreGivenetscoreMaximumvont toujours ensemble.
⚠️ Piège — Confondre
activityProgressetgradingProgress. Le premier décrit l’élève (a-t-il fini l’activité ?), le second décrit la note (est-elle définitive ?). Pour qu’une note s’affiche comme acquise dans le carnet, il faut typiquementgradingProgress=FullyGraded. Si ta correction est différée (relecture d’un prof), envoie d’abordgradingProgress=PendingManual, puis un second scoreFullyGradedune fois corrigé.
💡 Réflexe — Rends l’envoi idempotent-friendly : un même exercice re-soumis produit un nouveau score avec un
timestampplus récent, et la plateforme garde le dernier. Ne cherche pas à « écraser » manuellement ; laisse letimestampfaire l’arbitrage. Et journalise chaque envoi (élève, colonne, score, réponse HTTP) : quand un prof dit « la note n’est pas remontée », ton log tranche en trois secondes.
La plateforme répond généralement par un 200 OK (ou 201/202 selon les implémentations). Le score apparaît dans le carnet, en face de l’élève, dans la colonne de l’activité.
Créer une colonne (lineitem)
Parfois, aucune colonne n’est encore liée (pas de Deep Linking, activité créée « nue »). Tu la crées toi-même en POST sur le conteneur lineitems, avec le scope .../lti-ags/scope/lineitem :
POST /mod/lti/services.php/2/lineitems HTTP/1.1
Host: moodle.ecole.fr
Authorization: Bearer eyJ0eXAi...
Content-Type: application/vnd.ims.lis.v2.lineitem+json
{
"scoreMaximum": 20,
"label": "Calcul mental - fractions",
"resourceId": "ex-4815",
"resourceLinkId": "res-4815",
"tag": "exercice"
}scoreMaximumetlabelsont requis (le barème et le nom de la colonne).resourceId: ta référence interne (l’exerciceex-4815).resourceLinkId: relie la colonne à l’activité (leresource_link.iddu lancement) — pratique pour retrouver « la colonne de cette activité ».tag: une étiquette libre pour catégoriser (filtrer tes colonnes ensuite).
La réponse renvoie la colonne créée avec son id (une URL) : c’est ce id que tu utiliseras comme {lineitem} pour poster les scores.
💡 Réflexe — Avant de créer, cherche :
GET {lineitems}?resource_link_id=res-4815(ou filtre surtag/resource_id) pour voir si la colonne existe déjà. Créer aveuglément = colonnes en double dans le carnet, cauchemar de l’enseignant. Règle : trouver, sinon créer.
Relire les résultats
Pour lire les notes d’une colonne (afficher un suivi, recalculer une moyenne), GET sur {lineitem}/results, avec le scope .../lti-ags/scope/result.readonly :
GET /mod/lti/services.php/2/lineitems/5/lineitem/results HTTP/1.1
Host: moodle.ecole.fr
Authorization: Bearer eyJ0eXAi...
Accept: application/vnd.ims.lis.v2.resultcontainer+jsonRéponse (un conteneur de résultats, un par élève noté) :
[
{
"id": "https://moodle.ecole.fr/mod/lti/services.php/2/lineitems/5/results/u-2342",
"userId": "u-2342",
"resultScore": 17,
"resultMaximum": 20,
"scoreOf": "https://moodle.ecole.fr/mod/lti/services.php/2/lineitems/5/lineitem",
"comment": "Bravo. Revois les fractions equivalentes (question 7)."
}
]Chaque résultat porte userId, resultScore, resultMaximum, scoreOf (la colonne concernée) et l’éventuel comment. Note l’asymétrie : tu écris des Score (scoreGiven), tu lis des Result (resultScore). Ce ne sont pas les mêmes objets — le Score est un événement que tu déposes, le Result est l’état courant que la plateforme calcule.
⚠️ Piège — Vouloir modifier une note en
PUTsur un result. Lesresultssont en lecture seule (result.readonly). Pour changer une note, tu ne « modifies » pas le result : tu publies un nouveau Score (avec untimestampplus récent). La plateforme recalcule le result. Un seul chemin d’écriture :scores.
Le parcours complet, résumé
| Étape | Verbe HTTP | URL | Scope | Corps / réponse |
|---|---|---|---|---|
| Créer une colonne | POST | {lineitems} | .../scope/lineitem | objet Line Item |
| Lister les colonnes | GET | {lineitems} | .../scope/lineitem | conteneur de Line Items |
| Publier une note | POST | {lineitem}/scores | .../scope/score | objet Score |
| Lire les notes | GET | {lineitem}/results | .../scope/result.readonly | conteneur de Results |
🔌 Côté intégration — Le renvoi de note est le critère qui fait passer ton outil de « ressource sympa » à « brique pédagogique ». Un enseignant n’adopte durablement qu’un outil dont les notes atterrissent dans son carnet, à côté des autres, sans double saisie. C’est aussi le service AGS que la certification 1EdTech vérifie le plus scrupuleusement (barème respecté,
gradingProgresscorrect, idempotence partimestamp). Rate l’AGS, et tu restes hors des appels d’offres d’établissement.
🧭 Sur FormaCampus — Camille (élève de 6e A) termine « Calcul mental — fractions » avec 17/20 à 22 h 03. Un job du studio récupère l’endpoint AGS persisté au lancement, obtient un
access_token(scopescore), et POST un Score (userId= lesubde Camille,scoreGiven: 17,scoreMaximum: 20,activityProgress: "Completed",gradingProgress: "FullyGraded",timestamp). À 22 h 04, la note s’affiche dans le carnet Moodle de l’enseignant, dans la colonne créée au chapitre 10.2. Zéro ressaisie : le studio a écrit dans le carnet. C’est le moment où l’outil « vaut » un déploiement d’académie.
📚 La spec — AGS est spécifié par LTI Assignment and Grade Services (1EdTech). Le claim
.../lti-ags/claim/endpoint, l’objet Score (userId,scoreGiven,scoreMaximum,activityProgress,gradingProgress,timestamp,comment), l’objet Line Item et le conteneur Result ont des schémas et des types de média précis (application/vnd.ims.lis.v1.score+json,.../v2.lineitem+json,.../v2.resultcontainer+json). Valeurs exactes deactivityProgress/gradingProgresset détails sur1edtech.org.
✏️ Exercices
Exercice 1 — Écris le Score. Un élève (sub = u-8890) obtient 8 sur 10 à un quiz entièrement auto-corrigé, terminé le 3 juin 2026 à 9 h 15 UTC. Écris l’objet Score à poster sur {lineitem}/scores.
✅ Solution
{
"userId": "u-8890",
"scoreGiven": 8,
"scoreMaximum": 10,
"activityProgress": "Completed",
"gradingProgress": "FullyGraded",
"timestamp": "2026-06-03T09:15:00.000Z"
}userId = le sub. scoreGiven et scoreMaximum ensemble (8 sur 10). Activité terminée → Completed ; correction automatique et définitive → FullyGraded. timestamp en ISO 8601. Le comment est facultatif.
Exercice 2 — Diagnostique. Un enseignant se plaint : « L’exercice affiche 17/20 chez l’élève, mais mon carnet Moodle montre 17/100. » Cause probable et correction ?
✅ Solution
Le Score a été posté avec scoreGiven: 17 sans scoreMaximum (ou avec un scoreMaximum erroné). La plateforme a alors rapporté la note au barème de sa colonne (ici 100). Correction : toujours envoyer scoreMaximum: 20 avec le scoreGiven, et vérifier que le lineitem (ou son scoreMaximum à la création) correspond bien au barème de l’exercice. Republier un Score correct (avec un timestamp plus récent) remet la note d’équerre.
Exercice 3 — Trouver ou créer. Ton outil est lancé sur une activité sans colonne liée (lineitem absent du claim, seul lineitems est présent). Décris la marche à suivre avant de poster une note.
✅ Solution
- Obtenir un
access_tokenavec le scope.../lti-ags/scope/lineitem. 2. Chercher une colonne existante :GET {lineitems}filtré (parresource_link_idoutag). 3. Si elle existe, réutiliser sonid. 4. Sinon, la créer :POST {lineitems}avecscoreMaximum,label(etresourceLinkId/resourceId), récupérer leidrenvoyé. 5. Poster le Score sur{cet id}/scores(avec un jeton de scopescore). Trouver, sinon créer — jamais créer à l’aveugle.
🧠 Quiz de révision
1. Quelle est la différence entre lineitems et lineitem ?
lineitems et lineitem ?lineitems (pluriel) est le conteneur de toutes les colonnes du cours (GET pour lister, POST pour créer). lineitem (singulier) est une colonne, souvent celle liée à l’activité lancée ; c’est sur {lineitem}/scores qu’on poste les notes.
2. Sur quelle URL, avec quel scope, publie-t-on une note ?
POST {lineitem}/scores, avec le scope https://purl.imsglobal.org/spec/lti-ags/scope/score et le type de média application/vnd.ims.lis.v1.score+json.
3. Quels champs sont indispensables dans un Score ?
userId (le sub de l’élève), scoreGiven avec scoreMaximum, activityProgress, gradingProgress et timestamp. Le comment est optionnel.
4. Différence entre activityProgress et gradingProgress ?
activityProgress et gradingProgress ?activityProgress décrit l’avancement de l’élève dans l’activité (Initialized…Completed). gradingProgress décrit l’état de la correction (NotReady…FullyGraded). Pour une note définitive affichée, on vise gradingProgress: "FullyGraded".
5. Comment corrige-t-on une note déjà envoyée ?
On ne modifie pas les results (lecture seule). On publie un nouveau Score sur {lineitem}/scores avec un timestamp plus récent ; la plateforme recalcule le result et garde le plus récent.
Chapitre suivant : NRPS : lire le roster — connaître la classe sans que l’enseignant ressaisisse un seul nom.