> For the complete documentation index, see [llms.txt](https://ouvretaferme.gitbook.io/ouvretaferme/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://ouvretaferme.gitbook.io/ouvretaferme/endpoints/formations-et-faq/contenu-des-formations/modules.md).

# Modules

Les modules d'une formation, qui regroupent ses leçons. Un module supprimé emporte ses leçons ; supprimer celui qui porte les dernières leçons d'une formation publiée qu'un inscrit a suivie en entier est refusé en 412.

## Lister les leçons d'un module

> Scope requis : \`learning:read\`.

```json
{"openapi":"3.0.3","info":{"title":"API Ouvretaferme","version":"0.1beta"},"tags":[{"name":"Modules"}],"servers":[{"url":"https://api.ouvretaferme.org/0.1beta"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"Clé API personnelle ou jeton OAuth2 d'une application tierce, présenté dans l'en-tête `Authorization: Bearer`. Voir la section Authentification."}},"schemas":{"Lesson":{"type":"object","properties":{"id":{"type":"integer","format":"int64","readOnly":true},"course":{"type":"integer","format":"int64","description":"Identifiant d'un élément `Course`."},"chapter":{"type":"integer","format":"int64","description":"Module. Identifiant d'un élément `Chapter`."},"name":{"type":"string","minLength":1,"maxLength":100,"description":"Titre."},"objective":{"type":"string","minLength":1,"maxLength":250,"nullable":true,"description":"Objectif."},"format":{"type":"string","enum":["video","steps","text","empty"],"description":"Forme de la leçon, calculée sur son contenu. Elle vaut video quand la leçon porte une vidéo, sinon steps quand elle porte des étapes numérotées, sinon text quand elle porte un texte ou un quiz, et empty quand elle n'a rien à lire"},"minutes":{"type":"integer","nullable":true,"description":"Durée estimée."},"content":{"type":"string","nullable":true,"description":"Contenu de la leçon en HTML, lu dans les textes de ses blocs mis bout à bout"},"validation":{"type":"string","enum":["declared","quiz","deliverable","presence"],"description":"Validation."},"quizThreshold":{"type":"integer","nullable":true,"description":"Seuil de réussite."},"position":{"type":"integer"},"updatedAt":{"type":"string","pattern":"^[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}$","description":"Date et heure, au format AAAA-MM-JJ HH:MM:SS."}},"title":"Leçons","description":"Les leçons d'un module. Une leçon change de module à l'intérieur de sa formation, jamais d'une formation à une autre. Supprimer la dernière leçon d'une formation publiée qu'un inscrit a suivie en entier est refusé en 412. Le contenu s'écrit en HTML et se relit enveloppé dans une balise main, format de stockage de l'éditeur. Une leçon se compose de blocs, et son contenu se lit comme les textes de ces blocs mis bout à bout. Il ne s'écrit que sur une leçon sans bloc ou d'un seul bloc de texte, et l'écriture sur une leçon composée est refusée avec l'erreur Lesson::content.blocks. La forme (format) se lit et ne s'écrit pas : video quand la leçon porte une vidéo, sinon steps quand elle porte des étapes numérotées, sinon text quand elle porte un texte ou un quiz, sinon empty."},"Error":{"type":"object","properties":{"type":{"type":"string","enum":["authentication_error","resource_error","api_error"]},"code":{"type":"string"},"message":{"type":"string"}}}},"responses":{"ValidationError":{"description":"Un champ, un filtre ou un argument ne respecte pas les règles de validation, ou le corps n'est pas un objet JSON.","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Error"}}}}},"AuthenticationError":{"description":"Clé API ou jeton absent, invalide, expiré ou révoqué.","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Error"}}}}},"Forbidden":{"description":"Le jeton ne porte pas le scope requis, l'écriture n'est pas ouverte à cette structure, la structure n'a pas l'abonnement qui ouvre l'API, ou la personne qui a créé la clé n'administre plus la structure.","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Error"}}}}},"RateLimited":{"description":"Trop d'appels. L'en-tête `Retry-After` donne le nombre de secondes à attendre.","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Error"}}}},"headers":{"Retry-After":{"description":"Nombre de secondes à attendre avant le prochain appel.","schema":{"type":"integer"}}}}}},"paths":{"/chapter/{id}/lessonList":{"get":{"operationId":"getChapterByIdLessonList","tags":["Modules"],"summary":"Lister les leçons d'un module","description":"Scope requis : `learning:read`.","parameters":[{"name":"start","in":"query","required":false,"description":"Rang du premier élément rendu, à partir de 1.","schema":{"type":"integer","minimum":1,"default":1}},{"name":"limit","in":"query","required":false,"description":"Nombre d'éléments rendus, 100 par défaut et 500 au plus. À 0, seul le total est rendu.","schema":{"type":"integer","minimum":0,"maximum":500,"default":100}},{"name":"id","in":"path","required":true,"schema":{"type":"integer","format":"int64"}}],"responses":{"200":{"description":"Liste paginée des leçons.","content":{"application/json":{"schema":{"type":"object","properties":{"count":{"type":"integer","description":"Nombre d'éléments que retiennent les filtres, sans tenir compte de la pagination (`start`, `limit`, `afterId`, `beforeId`)."},"list":{"type":"array","items":{"$ref":"#/components/schemas/Lesson"}}}}}}},"400":{"$ref":"#/components/responses/ValidationError"},"401":{"$ref":"#/components/responses/AuthenticationError"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}}}}}}
```

## Consulter un module

> Scope requis : \`learning:read\`.

```json
{"openapi":"3.0.3","info":{"title":"API Ouvretaferme","version":"0.1beta"},"tags":[{"name":"Modules"}],"servers":[{"url":"https://api.ouvretaferme.org/0.1beta"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"Clé API personnelle ou jeton OAuth2 d'une application tierce, présenté dans l'en-tête `Authorization: Bearer`. Voir la section Authentification."}},"schemas":{"Chapter":{"type":"object","properties":{"id":{"type":"integer","format":"int64","readOnly":true},"course":{"type":"integer","format":"int64","description":"Identifiant d'un élément `Course`."},"phase":{"type":"integer","format":"int64","description":"Identifiant d'un élément `Phase`.","nullable":true},"name":{"type":"string","minLength":1,"maxLength":100,"description":"Nom du module, sans numéro, que l'écran ajoute devant le nom pour une formation en ligne"},"summary":{"type":"string","minLength":1,"maxLength":250,"nullable":true,"description":"Présentation."},"position":{"type":"integer"}},"title":"Modules","description":"Les modules d'une formation, qui regroupent ses leçons. Un module supprimé emporte ses leçons ; supprimer celui qui porte les dernières leçons d'une formation publiée qu'un inscrit a suivie en entier est refusé en 412."},"Error":{"type":"object","properties":{"type":{"type":"string","enum":["authentication_error","resource_error","api_error"]},"code":{"type":"string"},"message":{"type":"string"}}}},"responses":{"ValidationError":{"description":"Un champ, un filtre ou un argument ne respecte pas les règles de validation, ou le corps n'est pas un objet JSON.","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Error"}}}}},"AuthenticationError":{"description":"Clé API ou jeton absent, invalide, expiré ou révoqué.","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Error"}}}}},"Forbidden":{"description":"Le jeton ne porte pas le scope requis, l'écriture n'est pas ouverte à cette structure, la structure n'a pas l'abonnement qui ouvre l'API, ou la personne qui a créé la clé n'administre plus la structure.","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Error"}}}}},"NotFound":{"description":"Aucun élément avec cet identifiant pour votre structure.","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Error"}}}}},"RateLimited":{"description":"Trop d'appels. L'en-tête `Retry-After` donne le nombre de secondes à attendre.","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Error"}}}},"headers":{"Retry-After":{"description":"Nombre de secondes à attendre avant le prochain appel.","schema":{"type":"integer"}}}}}},"paths":{"/chapter/{id}":{"get":{"operationId":"getChapterById","tags":["Modules"],"summary":"Consulter un module","description":"Scope requis : `learning:read`.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","format":"int64"}}],"responses":{"200":{"description":"Module demandé.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Chapter"}}}},"400":{"$ref":"#/components/responses/ValidationError"},"401":{"$ref":"#/components/responses/AuthenticationError"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}}}}}}
```

## Modifier un module

> Scope requis : \`learning:write\`. Le corps porte tous les champs modifiables, ou seulement ceux que nomme sa clé \`properties\` (mise à jour partielle, voir la vue d'ensemble).

```json
{"openapi":"3.0.3","info":{"title":"API Ouvretaferme","version":"0.1beta"},"tags":[{"name":"Modules"}],"servers":[{"url":"https://api.ouvretaferme.org/0.1beta"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"Clé API personnelle ou jeton OAuth2 d'une application tierce, présenté dans l'en-tête `Authorization: Bearer`. Voir la section Authentification."}},"schemas":{"ChapterUpdate":{"type":"object","properties":{"phase":{"type":"integer","format":"int64","description":"Identifiant d'un élément `Phase`.","nullable":true},"name":{"type":"string","minLength":1,"maxLength":100,"description":"Nom du module, sans numéro, que l'écran ajoute devant le nom pour une formation en ligne"},"summary":{"type":"string","minLength":1,"maxLength":250,"nullable":true,"description":"Présentation."}},"required":["phase","name","summary"]},"Chapter":{"type":"object","properties":{"id":{"type":"integer","format":"int64","readOnly":true},"course":{"type":"integer","format":"int64","description":"Identifiant d'un élément `Course`."},"phase":{"type":"integer","format":"int64","description":"Identifiant d'un élément `Phase`.","nullable":true},"name":{"type":"string","minLength":1,"maxLength":100,"description":"Nom du module, sans numéro, que l'écran ajoute devant le nom pour une formation en ligne"},"summary":{"type":"string","minLength":1,"maxLength":250,"nullable":true,"description":"Présentation."},"position":{"type":"integer"}},"title":"Modules","description":"Les modules d'une formation, qui regroupent ses leçons. Un module supprimé emporte ses leçons ; supprimer celui qui porte les dernières leçons d'une formation publiée qu'un inscrit a suivie en entier est refusé en 412."},"Error":{"type":"object","properties":{"type":{"type":"string","enum":["authentication_error","resource_error","api_error"]},"code":{"type":"string"},"message":{"type":"string"}}}},"responses":{"ValidationError":{"description":"Un champ, un filtre ou un argument ne respecte pas les règles de validation, ou le corps n'est pas un objet JSON.","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Error"}}}}},"AuthenticationError":{"description":"Clé API ou jeton absent, invalide, expiré ou révoqué.","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Error"}}}}},"Forbidden":{"description":"Le jeton ne porte pas le scope requis, l'écriture n'est pas ouverte à cette structure, la structure n'a pas l'abonnement qui ouvre l'API, ou la personne qui a créé la clé n'administre plus la structure.","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Error"}}}}},"NotFound":{"description":"Aucun élément avec cet identifiant pour votre structure.","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Error"}}}}},"RateLimited":{"description":"Trop d'appels. L'en-tête `Retry-After` donne le nombre de secondes à attendre.","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Error"}}}},"headers":{"Retry-After":{"description":"Nombre de secondes à attendre avant le prochain appel.","schema":{"type":"integer"}}}}}},"paths":{"/chapter/{id}":{"put":{"operationId":"putChapterById","tags":["Modules"],"summary":"Modifier un module","description":"Scope requis : `learning:write`. Le corps porte tous les champs modifiables, ou seulement ceux que nomme sa clé `properties` (mise à jour partielle, voir la vue d'ensemble).","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","format":"int64"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ChapterUpdate"},{"type":"object","title":"Mise à jour partielle","description":"Les champs nommés dans `properties`, et eux seuls, chacun à sa nouvelle valeur.","required":["properties"],"properties":{"properties":{"type":"array","minItems":1,"items":{"type":"string","enum":["name","summary","phase"]}}},"additionalProperties":true}]}}}},"responses":{"200":{"description":"Module modifié.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Chapter"}}}},"400":{"$ref":"#/components/responses/ValidationError"},"401":{"$ref":"#/components/responses/AuthenticationError"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}}}}}}
```

## Supprimer un module

> Scope requis : \`learning:write\`.

```json
{"openapi":"3.0.3","info":{"title":"API Ouvretaferme","version":"0.1beta"},"tags":[{"name":"Modules"}],"servers":[{"url":"https://api.ouvretaferme.org/0.1beta"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"Clé API personnelle ou jeton OAuth2 d'une application tierce, présenté dans l'en-tête `Authorization: Bearer`. Voir la section Authentification."}},"responses":{"AuthenticationError":{"description":"Clé API ou jeton absent, invalide, expiré ou révoqué.","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Error"}}}}},"Forbidden":{"description":"Le jeton ne porte pas le scope requis, l'écriture n'est pas ouverte à cette structure, la structure n'a pas l'abonnement qui ouvre l'API, ou la personne qui a créé la clé n'administre plus la structure.","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Error"}}}}},"NotFound":{"description":"Aucun élément avec cet identifiant pour votre structure.","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Error"}}}}},"RateLimited":{"description":"Trop d'appels. L'en-tête `Retry-After` donne le nombre de secondes à attendre.","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Error"}}}},"headers":{"Retry-After":{"description":"Nombre de secondes à attendre avant le prochain appel.","schema":{"type":"integer"}}}}},"schemas":{"Error":{"type":"object","properties":{"type":{"type":"string","enum":["authentication_error","resource_error","api_error"]},"code":{"type":"string"},"message":{"type":"string"}}}}},"paths":{"/chapter/{id}":{"delete":{"operationId":"deleteChapterById","tags":["Modules"],"summary":"Supprimer un module","description":"Scope requis : `learning:write`.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","format":"int64"}}],"responses":{"200":{"description":"Supprimé. Le corps de la réponse est vide."},"401":{"$ref":"#/components/responses/AuthenticationError"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"412":{"description":"Condition métier interdisant la suppression.","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Error"}}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}}}}
```

## Créer un module

> Scope requis : \`learning:write\`.

```json
{"openapi":"3.0.3","info":{"title":"API Ouvretaferme","version":"0.1beta"},"tags":[{"name":"Modules"}],"servers":[{"url":"https://api.ouvretaferme.org/0.1beta"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"Clé API personnelle ou jeton OAuth2 d'une application tierce, présenté dans l'en-tête `Authorization: Bearer`. Voir la section Authentification."}},"schemas":{"ChapterWrite":{"type":"object","properties":{"course":{"type":"integer","format":"int64","description":"Identifiant d'un élément `Course`."},"phase":{"type":"integer","format":"int64","description":"Identifiant d'un élément `Phase`.","nullable":true},"name":{"type":"string","minLength":1,"maxLength":100,"description":"Nom du module, sans numéro, que l'écran ajoute devant le nom pour une formation en ligne"},"summary":{"type":"string","minLength":1,"maxLength":250,"nullable":true,"description":"Présentation."}},"required":["course","phase","name","summary"]},"Chapter":{"type":"object","properties":{"id":{"type":"integer","format":"int64","readOnly":true},"course":{"type":"integer","format":"int64","description":"Identifiant d'un élément `Course`."},"phase":{"type":"integer","format":"int64","description":"Identifiant d'un élément `Phase`.","nullable":true},"name":{"type":"string","minLength":1,"maxLength":100,"description":"Nom du module, sans numéro, que l'écran ajoute devant le nom pour une formation en ligne"},"summary":{"type":"string","minLength":1,"maxLength":250,"nullable":true,"description":"Présentation."},"position":{"type":"integer"}},"title":"Modules","description":"Les modules d'une formation, qui regroupent ses leçons. Un module supprimé emporte ses leçons ; supprimer celui qui porte les dernières leçons d'une formation publiée qu'un inscrit a suivie en entier est refusé en 412."},"Error":{"type":"object","properties":{"type":{"type":"string","enum":["authentication_error","resource_error","api_error"]},"code":{"type":"string"},"message":{"type":"string"}}}},"responses":{"ValidationError":{"description":"Un champ, un filtre ou un argument ne respecte pas les règles de validation, ou le corps n'est pas un objet JSON.","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Error"}}}}},"AuthenticationError":{"description":"Clé API ou jeton absent, invalide, expiré ou révoqué.","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Error"}}}}},"Forbidden":{"description":"Le jeton ne porte pas le scope requis, l'écriture n'est pas ouverte à cette structure, la structure n'a pas l'abonnement qui ouvre l'API, ou la personne qui a créé la clé n'administre plus la structure.","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Error"}}}}},"RateLimited":{"description":"Trop d'appels. L'en-tête `Retry-After` donne le nombre de secondes à attendre.","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Error"}}}},"headers":{"Retry-After":{"description":"Nombre de secondes à attendre avant le prochain appel.","schema":{"type":"integer"}}}}}},"paths":{"/chapter":{"post":{"operationId":"postChapter","tags":["Modules"],"summary":"Créer un module","description":"Scope requis : `learning:write`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChapterWrite"}}}},"responses":{"201":{"description":"Module créé.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Chapter"}}}},"400":{"$ref":"#/components/responses/ValidationError"},"401":{"$ref":"#/components/responses/AuthenticationError"},"403":{"$ref":"#/components/responses/Forbidden"},"412":{"description":"Condition métier interdisant la création.","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Error"}}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}}}}
```
