Récupérer la feuille de présence d'un cours — l'émargement, pour un financeur — en JSON, en CSV ou prête à imprimer. Ces routes s'appellent avec une clé d'intégration : voir Conventions.
Toutes sont sous /api/institutions/:institutionId, noté … dans les titres ci-dessous, où :institutionId est l'identifiant de l'établissement de la clé, rendu par GET /api/me.
Quand une feuille existe
Une feuille est arrêtée une fois, et ne change plus ensuite :
- automatiquement, quand la fenêtre d'accès du cours est passée — un balayage passe toutes les 15 minutes, la feuille est donc arrêtée au plus tard 15 minutes après
closesAt; - ou à la main, sans attendre le balayage, par POST …/lessons/:lessonId/attendance/close.
Un cours sans classe attendue et sans aucun participant n'a pas de feuille : il n'y a personne à attendre ni personne de venu. Un cours avec une classe a toujours sa feuille, même si personne n'est venu.
L'arrêt d'une feuille émet l'événement attendance.closed, qui porte, dans sessionId, l'identifiant du cours à utiliser dans les routes ci-dessous — le :lessonId : voir Webhooks.
L'objet Feuille
{
"id": "e3b0c442-98fc-4c14-9afb-f4c8996fb924",
"sessionId": "9214f192-5c1e-4a0e-9b6e-1c2d3e4f5a6b",
"roomName": "Salle 204",
"groupName": "BTS SIO 1",
"siteName": "Campus de Douala",
"title": "Mathématiques",
"startsAt": "2026-10-14T08:00:00.000Z",
"durationMinutes": 120,
"expectedCount": 28,
"presentCount": 26,
"closedAt": "2026-10-14T10:30:00.000Z",
"signature": "Q2hhcnNldCBsaWtlIGEgc2lnbmF0dXJlIGhlcmUuLi4"
}
| Champ | Type | Description |
|---|---|---|
id | uuid | Identifiant de la feuille |
sessionId | uuid ou null | Le cours — son :lessonId |
roomName | string | Le nom de la salle, recopié à l'arrêt ; (salle supprimée) si la salle n'existait plus |
groupName | string ou null | Le nom de la classe, recopié à l'arrêt ; pour un cours diffusé à d'autres classes, leurs noms joints par + ; null si aucune |
siteName | string ou null | Le nom du centre de la salle, recopié à l'arrêt ; null si la salle n'est rattachée à aucun centre. Hors du code de vérification : c'est un classement, pas un fait de présence |
title | string ou null | L'intitulé du cours |
startsAt | datetime | L'heure de début prévue |
durationMinutes | integer | La durée prévue, en minutes, au moins 1 |
expectedCount | integer | L'effectif attendu : les membres des classes attendues ; à défaut, le nombre d'invités de la salle ; à défaut, 0 |
presentCount | integer | Le nombre de personnes venues au moins une fois : c'est le nombre de lignes |
closedAt | datetime ou null | Le moment de l'arrêt |
signature | string ou null | Le code de vérification de la feuille. Voir Le code de vérification |
Les noms sont RECOPIÉS dans la feuille, et non liés : une salle supprimée ou une classe archivée ne change pas une feuille déjà arrêtée.
L'objet Ligne
Une ligne par personne. Les connexions successives d'une même personne — un même compte, ou, sans compte, une même connexion reprise — sont fusionnées.
| Champ | Type | Description |
|---|---|---|
accountId | uuid ou null | Le compte de la personne ; null pour un participant venu sans compte |
displayName | string | Le nom affiché pendant le cours |
email | string ou null | L'adresse du compte, recopiée à l'arrêt ; null sans compte, ou pour un compte sans courriel |
firstSeenAt | datetime | La première entrée dans la salle |
lastSeenAt | datetime | La dernière sortie, bornée à la fermeture de la salle |
presentSeconds | integer | Le temps de présence cumulé, en secondes, sans compter le temps après closesAt |
interruptions | integer | Le nombre de coupures d'au moins 60 secondes entre deux connexions successives |
quality | string | La qualité de réception vécue : good, fair, poor ou unknown. Quand la personne s'est connectée plusieurs fois, c'est la plus mauvaise de ses connexions. Voir Qualité vécue |
Les lignes sont rendues dans l'ordre alphabétique de displayName, puis par première entrée.
Qualité vécue
Elle mesure ce que le serveur a réussi à transmettre à la personne. Le serveur note de 0 à 10 chaque flux qu'elle reçoit, audio comme vidéo ; à chaque instant, c'est le plus mauvais des flux reçus qui compte. Une note inférieure à 5 est une réception dégradée, de 5 à 7 une réception moyenne, de 8 à 10 une bonne réception. Le verdict est pondéré par le temps passé à chaque niveau, et non moyenné.
| Valeur | Règle |
|---|---|
poor | Réception dégradée pendant au moins un quart du temps observé |
fair | Sinon : réception moyenne ou dégradée pendant au moins un quart du temps, ou au moins 2 minutes cumulées de réception dégradée |
good | Sinon |
unknown | Moins d'une minute observée : une présence trop brève, ou une personne qui n'a reçu aucun flux, seule dans la salle par exemple |
Le code de vérification
Le champ signature porte le code de vérification de la feuille : un HMAC-SHA256 de son contenu, encodé en base64url — 43 caractères. Il est calculé par le serveur à l'arrêt, avec une clé secrète qu'il est seul à connaître. Vous ne pouvez pas le recalculer de votre côté : archivez-le avec la feuille.
N'importe qui peut en revanche le faire contrôler, sans compte, sur la page publique /verification de l'instance — par exemple https://reunion.votre-etablissement.fr/verification?code=Q2hhcnNldCBs…. La page dit si la feuille est authentique ou si elle a été modifiée depuis son arrêt, et rappelle l'établissement, la salle, le cours et les effectifs. Un financeur qui tient la feuille imprimée peut ainsi la vérifier lui-même.
GET …/lessons/:lessonId/attendance
La feuille en JSON. Exige attendance.read — la portée attendance:read.
| Paramètre de chemin | Type | Description |
|---|---|---|
lessonId | uuid | L'identifiant MeetingHub du cours, celui de sessionId dans le webhook |
Réponse 200 :
{
"sheet": { "id": "e3b0c442-…", "presentCount": 26, "…": "…" },
"lines": [
{
"accountId": "5a74f72e-81af-402c-a2af-a5ebd35b7bba",
"displayName": "Awa Diallo",
"email": "awa@ecole.fr",
"firstSeenAt": "2026-10-14T07:58:12.000Z",
"lastSeenAt": "2026-10-14T10:01:40.000Z",
"presentSeconds": 7240,
"interruptions": 1,
"quality": "good"
}
],
"filename": "emargement-salle-204-2026-10-14.csv"
}
| Champ | Type | Description |
|---|---|---|
sheet | Feuille | Voir L'objet Feuille |
lines | Ligne[] | Voir L'objet Ligne ; vide si personne n'est venu |
filename | string | Le nom de fichier proposé pour l'export CSV : emargement-, le nom de la salle simplifié, puis la date de début au format AAAA-MM-JJ |
| Code | Corps | code | Cause |
|---|---|---|---|
403 | cet accès ne permet pas cela — il faut : attendance.read | forbidden | Portée absente |
404 | établissement inconnu | not-found | :institutionId n'est pas l'établissement de la clé |
409 | feuille non encore arrêtée — attendez l’alerte attendance.closed | sheet.not-closed | Aucune feuille pour ce lessonId : le cours n'est pas encore terminé, n'a rien à relever, n'existe pas dans l'établissement, ou l'identifiant est mal formé |
Avec une session plutôt qu'une clé, un directeur de centre ne lit que les feuilles des cours de ses centres ; pour un autre cours, la réponse est 404, cours inconnu.
GET …/lessons/:lessonId/attendance.csv
La même feuille, en CSV. Exige attendance.read. Mêmes paramètres et mêmes erreurs que la route JSON.
Réponse 200 :
| En-tête | Valeur |
|---|---|
Content-Type | text/csv; charset=utf-8 |
Content-Disposition | attachment; filename="emargement-salle-204-2026-10-14.csv" |
Format du fichier
| Propriété | Valeur |
|---|---|
| Encodage | UTF-8, avec l'indicateur d'ordre des octets en tête : Excel en français l'ouvre sans conversion |
| Séparateur | ; |
| Fin de ligne | \r\n |
| Guillemets | Un champ qui contient ;, " ou un saut de ligne est entouré de guillemets ; ses guillemets sont doublés |
| Formules | Un champ qui commence par =, +, -, @, une tabulation ou un retour chariot est précédé d'une apostrophe, pour qu'un tableur ne l'exécute pas |
| Dates | JJ/MM/AAAA HH:MM, en UTC |
Le fichier commence par un bloc d'en-tête, une ligne vide, la ligne des colonnes, puis une ligne par personne — ou une ligne Aucun participant.
| Ligne d'en-tête | Contenu |
|---|---|
Feuille de présence | Titre, seul sur sa ligne |
Salle | roomName |
Classe | groupName, ou — |
Cours | title, ou — |
Début | startsAt |
Durée prévue | durationMinutes, suivi de min |
Effectif attendu | expectedCount |
Présents | presentCount |
Clôturée le | closedAt, ou non clôturée |
Code de vérification | signature, ou — |
| Colonne | Contenu |
|---|---|
Nom | displayName |
Courriel | email, vide sans courriel |
Première entrée | firstSeenAt, au format JJ/MM/AAAA HH:MM |
Dernière sortie | lastSeenAt, heure seule au format HH:MM |
Présence (minutes) | presentSeconds converti en minutes, arrondi à l'entier |
Présence (h:mm) | La même durée, au format H:MM |
Interruptions | interruptions |
Assiduité | La part de la durée prévue passée dans la salle, plafonnée à 100 %, avec une décimale et une virgule : 87,5 % |
Qualité vécue | bonne, moyenne, dégradée ou inconnue — pour good, fair, poor et unknown |
GET …/lessons/:lessonId/attendance.html
La même feuille, prête à imprimer. Exige attendance.read. Mêmes paramètres et mêmes erreurs que la route JSON.
Réponse 200 :
| En-tête | Valeur |
|---|---|
Content-Type | text/html; charset=utf-8 |
Content-Disposition | inline; filename="emargement-salle-204-2026-10-14.html" |
Une page autonome, au format A4, sans aucune ressource extérieure : elle s'ouvre hors ligne et s'imprime en PDF depuis n'importe quel navigateur. L'en-tête du tableau se répète sur chaque page imprimée, et une ligne n'est jamais coupée entre deux pages. Elle porte un cadre pour le visa du responsable, le code de vérification de la feuille et, quand l'instance connaît son adresse publique, l'adresse de la page où le vérifier.
POST …/lessons/:lessonId/attendance/close
Arrête la feuille sans attendre le balayage. Exige schedule.write — la portée schedule:write. Aucun corps.
Réponse 201 :
{ "sheet": { "id": "e3b0c442-…", "closedAt": "2026-10-14T10:05:00.000Z", "…": "…" }
}
| Champ | Type | Description |
|---|---|---|
sheet | Feuille | La feuille arrêtée. Si elle l'était déjà, elle est rendue telle quelle, sans être recalculée |
Une feuille arrêtée avant la fin du cours ne compte que les présences enregistrées jusque-là : elle ne change plus ensuite. Le premier arrêt émet attendance.closed ; un second appel ne l'émet pas une nouvelle fois.
| Code | Corps | code | Cause |
|---|---|---|---|
403 | cet accès ne permet pas cela — il faut : schedule.write | forbidden | Portée absente |
404 | cours inconnu | not-found | Aucun cours de cet identifiant dans l'établissement, ou lessonId mal formé |
409 | rien à relever : ni classe attendue, ni présence | sheet.empty | Le cours n'a ni classe attendue ni participant : il n'a pas de feuille |
Toutes les feuilles d'un coup
GET …/export/attendance.csv rend toutes les feuilles de l'établissement en un seul fichier, une ligne par personne et par cours. Elle exige institution.export, que seul le Promoteur de l'établissement détient, avec sa session : une clé ne l'atteint pas. Elle reste ouverte quand l'établissement est en pause, pour qu'il puisse emporter ses feuilles.
Les anciennes adresses
GET /api/integration/sessions/:id/attendance, et ses variantes .csv et .html, répondent encore avec une clé d'établissement, comme alias dépréciés de ces routes : chaque réponse porte Deprecation: true et un en-tête Link vers la route de /api. Passez à /api.