{"components":{"responses":{},"schemas":{"AdminCourseListItem":{"additionalProperties":false,"properties":{"id":{"description":"Course id","format":"uuid","type":"string"},"slug":{"description":"Course slug","type":"string"},"stages":{"items":{"$ref":"#/components/schemas/AdminStage"},"type":"array"},"status":{"description":"`draft` is visible only to admins; `published` is public","enum":["draft","published"],"type":"string"},"summary":{"description":"Description","type":"string"},"title":{"description":"Course title","type":"string"}},"required":["id","status","title","summary","stages","slug"],"title":"AdminCourseListItem","type":"object"},"Book":{"additionalProperties":false,"description":"A course (or one stage) with every lesson body, for building a PDF or ebook","properties":{"course":{"$ref":"#/components/schemas/CourseSummary"},"stages":{"items":{"$ref":"#/components/schemas/BookStage"},"type":"array"},"updated_at":{"description":"Newest lesson edit in the book: changes whenever its content does","format":"date-time","nullable":true,"type":"string"}},"required":["course","stages","updated_at"],"title":"Book","type":"object"},"CertificateRevocationResult":{"additionalProperties":false,"description":"The certificate is revoked","properties":{"certificate_revoked_at":{"description":"When it was revoked","format":"date-time","type":"string"}},"required":["certificate_revoked_at"],"title":"CertificateRevocationResult","type":"object"},"BookLesson":{"additionalProperties":false,"description":"A lesson with its text and last edit time","properties":{"body":{"description":"Lesson text, Markdown","type":"string"},"id":{"description":"Lesson id","format":"uuid","type":"string"},"position":{"description":"Order within the stage, from 1","type":"integer"},"slug":{"description":"Lesson slug, unique within its stage","type":"string"},"summary":{"description":"One-line description","type":"string"},"title":{"description":"Lesson title","type":"string"},"updated_at":{"description":"Last edit","format":"date-time","type":"string"}},"required":["id","position","title","body","summary","slug","updated_at"],"title":"BookLesson","type":"object"},"BookModule":{"additionalProperties":false,"properties":{"lessons":{"items":{"$ref":"#/components/schemas/BookLesson"},"type":"array"},"title":{"description":"Module title","type":"string"}},"required":["title","lessons"],"title":"BookModule","type":"object"},"BundleCheatsheet":{"description":"The course's quick-reference sheet","properties":{"body":{"description":"Text, Markdown (at most 50,000 characters)","type":"string"},"status":{"description":"`draft` is visible only to admins; `published` is public","enum":["draft","published"],"type":"string"},"title":{"description":"Title","maxLength":120,"type":"string"}},"required":["title"],"title":"BundleCheatsheet","type":"object"},"BundleModule":{"description":"A group of lessons in a course file","properties":{"lessons":{"items":{"$ref":"#/components/schemas/BundleLesson"},"type":"array"},"title":{"description":"Module title","type":"string"}},"required":["title"],"title":"BundleModule","type":"object"},"CourseSummary":{"additionalProperties":false,"description":"A published course","properties":{"levels":{"description":"Levels that have a published stage (only when the stages are loaded)","items":{"description":"Stage level, in progression order","enum":["beginner","intermediate","advanced"],"type":"string"},"type":"array"},"slug":{"description":"Course slug, used in URLs","type":"string"},"summary":{"description":"One-paragraph description","type":"string"},"title":{"description":"Course title","type":"string"}},"required":["slug","title","summary"],"title":"CourseSummary","type":"object"},"AdminPrerequisites":{"additionalProperties":false,"description":"A stage's requirements","properties":{"candidates":{"description":"Stages that could be added without a cycle","items":{"$ref":"#/components/schemas/AdminStageWithCourse"},"type":"array"},"required":{"description":"Stages recommended first","items":{"additionalProperties":false,"properties":{"automatic":{"description":"True for the course's own earlier stages; those cannot be removed","type":"boolean"},"course_id":{"description":"Course id","format":"uuid","type":"string"},"course_slug":{"description":"Course slug","type":"string"},"course_title":{"description":"Course title","type":"string"},"id":{"description":"Stage id","format":"uuid","type":"string"},"level":{"description":"Stage level, in progression order","enum":["beginner","intermediate","advanced"],"type":"string"},"status":{"description":"`draft` is visible only to admins; `published` is public","enum":["draft","published"],"type":"string"},"title":{"description":"Stage title","type":"string"}},"required":["id","status","level","title","course_id","course_slug","course_title","automatic"],"type":"object"},"type":"array"},"stage_id":{"description":"The stage these requirements are for","format":"uuid","type":"string"}},"required":["required","candidates","stage_id"],"title":"AdminPrerequisites","type":"object"},"AdminLearnerCertificate":{"additionalProperties":false,"description":"A certificate a learner holds","properties":{"course_slug":{"description":"Course slug when it was issued","type":"string"},"course_title":{"description":"Course title when it was issued","type":"string"},"id":{"description":"Certificate id","format":"uuid","type":"string"},"issued_at":{"description":"When it was issued","format":"date-time","type":"string"},"recipient_name":{"description":"The name printed on it","type":"string"},"revoked_at":{"description":"When it was revoked; null while valid","format":"date-time","nullable":true,"type":"string"}},"required":["id","course_slug","course_title","issued_at","recipient_name","revoked_at"],"title":"AdminLearnerCertificate","type":"object"},"AdminStageTree":{"additionalProperties":false,"properties":{"course_id":{"description":"Course id","format":"uuid","type":"string"},"id":{"description":"Stage id","format":"uuid","type":"string"},"lessons":{"items":{"$ref":"#/components/schemas/AdminLessonOutline"},"type":"array"},"level":{"description":"Stage level, in progression order","enum":["beginner","intermediate","advanced"],"type":"string"},"modules":{"items":{"$ref":"#/components/schemas/AdminModule"},"type":"array"},"status":{"description":"`draft` is visible only to admins; `published` is public","enum":["draft","published"],"type":"string"},"summary":{"description":"What the stage is about","type":"string"},"title":{"description":"Stage title","type":"string"}},"required":["id","status","level","modules","title","summary","course_id","lessons"],"title":"AdminStageTree","type":"object"},"AdminStage":{"additionalProperties":false,"description":"A stage, drafts included","properties":{"course_id":{"description":"Course id","format":"uuid","type":"string"},"id":{"description":"Stage id","format":"uuid","type":"string"},"level":{"description":"Stage level, in progression order","enum":["beginner","intermediate","advanced"],"type":"string"},"status":{"description":"`draft` is visible only to admins; `published` is public","enum":["draft","published"],"type":"string"},"summary":{"description":"What the stage is about","type":"string"},"title":{"description":"Stage title","type":"string"}},"required":["id","status","level","title","summary","course_id"],"title":"AdminStage","type":"object"},"StageSummary":{"additionalProperties":false,"description":"A published stage","properties":{"id":{"description":"Stage id","format":"uuid","type":"string"},"level":{"description":"Stage level, in progression order","enum":["beginner","intermediate","advanced"],"type":"string"},"summary":{"description":"What the stage is about","type":"string"},"title":{"description":"Stage title","type":"string"}},"required":["id","level","title","summary"],"title":"StageSummary","type":"object"},"AnalyticsLearner":{"additionalProperties":false,"description":"A learner and what they have done. Personal data: opt-in view only.","properties":{"badges":{"description":"Badges earned","type":"integer"},"certificates":{"description":"Certificates issued","type":"integer"},"email":{"description":"As last seen in their token","nullable":true,"type":"string"},"first_seen_at":{"description":"First visit recorded","format":"date-time","nullable":true,"type":"string"},"id":{"description":"The learner's Keycloak subject","type":"string"},"last_active_at":{"description":"Latest visit or lesson completion","format":"date-time","nullable":true,"type":"string"},"lessons_completed":{"description":"Published lessons completed","type":"integer"},"name":{"description":"As last seen in their token (null if not seen since names were recorded)","nullable":true,"type":"string"},"stages_completed":{"description":"Stages finished","type":"integer"},"stages_started":{"description":"Stages started","type":"integer"}},"required":["id","name","badges","lessons_completed","stages_completed","certificates","email","first_seen_at","last_active_at","stages_started"],"title":"AnalyticsLearner","type":"object"},"Error":{"additionalProperties":false,"description":"401, 403 and 404 responses","properties":{"errors":{"additionalProperties":false,"properties":{"detail":{"description":"What went wrong, e.g. `Not Found`","type":"string"}},"required":["detail"],"type":"object"}},"required":["errors"],"title":"Error","type":"object"},"CertificateInput":{"description":"Issue the certificate for a badge","properties":{"name":{"description":"The name to print (1 to 80 characters); fixed once issued","maxLength":80,"type":"string"}},"required":["name"],"title":"CertificateInput","type":"object"},"ImportRequest":{"description":"Import a course file","properties":{"bundle":{"$ref":"#/components/schemas/CourseBundle"},"dry_run":{"default":false,"description":"Only check the file and report what would happen; write nothing.","type":"boolean"},"mode":{"default":"create","description":"`create` refuses a course whose slug already exists (409). `update` updates it in place: matching stages, modules and lessons are overwritten, others kept.","enum":["create","update"],"type":"string"},"status":{"default":"keep","description":"`keep` uses each item's status from the file; `draft` or `published` forces everything.","enum":["keep","draft","published"],"type":"string"}},"required":["bundle"],"title":"ImportRequest","type":"object"},"AdminStageWithCourse":{"additionalProperties":false,"properties":{"course_id":{"description":"Course id","format":"uuid","type":"string"},"course_slug":{"description":"Course slug","type":"string"},"course_title":{"description":"Course title","type":"string"},"id":{"description":"Stage id","format":"uuid","type":"string"},"level":{"description":"Stage level, in progression order","enum":["beginner","intermediate","advanced"],"type":"string"},"status":{"description":"`draft` is visible only to admins; `published` is public","enum":["draft","published"],"type":"string"},"title":{"description":"Stage title","type":"string"}},"required":["id","status","level","title","course_id","course_slug","course_title"],"title":"AdminStageWithCourse","type":"object"},"ModuleOutline":{"additionalProperties":false,"description":"A group of lessons","properties":{"lessons":{"items":{"$ref":"#/components/schemas/LessonOutline"},"type":"array"},"position":{"description":"Order within the stage","type":"integer"},"title":{"description":"Module title","type":"string"}},"required":["position","title","lessons"],"title":"ModuleOutline","type":"object"},"ModuleInput":{"description":"Create or update a module","properties":{"position":{"description":"Order within the stage, from 1","minimum":1,"type":"integer"},"title":{"description":"Title","type":"string"}},"required":["title","position"],"title":"ModuleInput","type":"object"},"LessonInput":{"description":"Create or update a lesson. `expected_version` applies to updates.","properties":{"body":{"description":"Lesson text, Markdown","type":"string"},"expected_version":{"description":"On update: the `version` you loaded. A stale save is refused with 409.","type":"integer"},"module_id":{"description":"A module of the same stage, or null","format":"uuid","nullable":true,"type":"string"},"position":{"description":"Order within the stage, from 1","minimum":1,"type":"integer"},"slug":{"description":"Lowercase letters, digits and single hyphens","pattern":"^[a-z0-9]+(-[a-z0-9]+)*$","type":"string"},"status":{"description":"`draft` is visible only to admins; `published` is public","enum":["draft","published"],"type":"string"},"summary":{"description":"One-line description","type":"string"},"title":{"description":"Title","type":"string"}},"required":["slug","position","title"],"title":"LessonInput","type":"object"},"Neighbour":{"additionalProperties":false,"description":"The previous or next lesson","properties":{"slug":{"description":"Lesson slug","type":"string"},"title":{"description":"Lesson title","type":"string"}},"required":["title","slug"],"title":"Neighbour","type":"object"},"AdminLesson":{"additionalProperties":false,"description":"A lesson with its text, for editing","properties":{"body":{"description":"Lesson text, Markdown","type":"string"},"course_id":{"description":"Course id (present when read by id)","format":"uuid","nullable":true,"type":"string"},"id":{"description":"Lesson id","format":"uuid","type":"string"},"module_id":{"description":"Module id, or null for a lesson outside any module","format":"uuid","nullable":true,"type":"string"},"position":{"description":"Order within the stage","type":"integer"},"slug":{"description":"Lesson slug","type":"string"},"stage_id":{"description":"Stage id","format":"uuid","type":"string"},"status":{"description":"`draft` is visible only to admins; `published` is public","enum":["draft","published"],"type":"string"},"summary":{"description":"One-line description","type":"string"},"title":{"description":"Lesson title","type":"string"},"updated_at":{"description":"Last edit","format":"date-time","type":"string"},"version":{"description":"Send back as `expected_version` to detect edit conflicts","type":"integer"}},"required":["id","position","status","version","title","body","summary","course_id","stage_id","module_id","slug","updated_at"],"title":"AdminLesson","type":"object"},"LessonOutline":{"additionalProperties":false,"description":"A lesson without its text","properties":{"id":{"description":"Lesson id","format":"uuid","type":"string"},"position":{"description":"Order within the stage, from 1","type":"integer"},"slug":{"description":"Lesson slug, unique within its stage","type":"string"},"summary":{"description":"One-line description","type":"string"},"title":{"description":"Lesson title","type":"string"}},"required":["id","position","title","summary","slug"],"title":"LessonOutline","type":"object"},"AnalyticsTotals":{"additionalProperties":false,"properties":{"active_learners":{"description":"Visited or completed a lesson in the range","type":"integer"},"all_time":{"description":"True when the range is `all`","type":"boolean"},"badges_earned":{"description":"Badges earned in the range","type":"integer"},"certificates_issued":{"description":"Certificates issued in the range","type":"integer"},"learners":{"description":"Everyone who has signed in or made progress, ever","type":"integer"},"lessons_completed":{"description":"Published lessons completed in the range","type":"integer"},"new_learners":{"description":"First seen in the range (only counts visits since profiles began)","type":"integer"},"stages_completed":{"description":"Stages finished in the range","type":"integer"}},"required":["learners","active_learners","all_time","badges_earned","certificates_issued","lessons_completed","new_learners","stages_completed"],"title":"AnalyticsTotals","type":"object"},"StageProgress":{"additionalProperties":false,"description":"A learner's progress in one stage","properties":{"completed_at":{"description":"When every lesson was completed","format":"date-time","nullable":true,"type":"string"},"completed_lessons":{"description":"Published lessons completed","type":"integer"},"enrolled_at":{"description":"When the learner started the stage","format":"date-time","nullable":true,"type":"string"},"stage_id":{"description":"Stage id","format":"uuid","type":"string"},"total_lessons":{"description":"Published lessons in the stage","type":"integer"}},"required":["stage_id","total_lessons","completed_at","completed_lessons","enrolled_at"],"title":"StageProgress","type":"object"},"Certificate":{"additionalProperties":false,"description":"A certificate of completion. The learner's account id is never exposed.","properties":{"course_slug":{"description":"Course slug when it was issued","type":"string"},"course_title":{"description":"Course title when it was issued","type":"string"},"id":{"description":"Certificate id: also the public verification id","format":"uuid","type":"string"},"issued_at":{"description":"When it was issued","format":"date-time","type":"string"},"recipient_name":{"description":"The name printed on it","type":"string"},"revoked_at":{"description":"When an admin revoked it; null while it is valid","format":"date-time","nullable":true,"type":"string"}},"required":["id","course_slug","course_title","issued_at","recipient_name","revoked_at"],"title":"Certificate","type":"object"},"BadgeResetResult":{"additionalProperties":false,"description":"What a badge reset did","properties":{"badge_removed":{"description":"Always true","type":"boolean"},"certificate_revoked":{"description":"Whether a certificate of the badge was revoked","type":"boolean"}},"required":["badge_removed","certificate_revoked"],"title":"BadgeResetResult","type":"object"},"AdminPathListItem":{"additionalProperties":false,"properties":{"id":{"description":"Path id","format":"uuid","type":"string"},"slug":{"description":"Path slug","type":"string"},"stage_count":{"description":"Stages in the path","type":"integer"},"status":{"description":"`draft` is visible only to admins; `published` is public","enum":["draft","published"],"type":"string"},"summary":{"description":"What the path is for","type":"string"},"title":{"description":"Path title","type":"string"}},"required":["id","status","title","summary","slug","stage_count"],"title":"AdminPathListItem","type":"object"},"AdminModule":{"additionalProperties":false,"properties":{"id":{"description":"Module id","format":"uuid","type":"string"},"position":{"description":"Order within the stage, from 1","type":"integer"},"stage_id":{"description":"Stage id","format":"uuid","type":"string"},"title":{"description":"Module title","type":"string"}},"required":["id","position","title","stage_id"],"title":"AdminModule","type":"object"},"AdminLearnerBadge":{"additionalProperties":false,"description":"A badge a learner holds","properties":{"awarded_at":{"description":"When it was awarded","format":"date-time","type":"string"},"certificate_id":{"description":"The certificate issued for it, if any","format":"uuid","nullable":true,"type":"string"},"course_slug":{"description":"Course slug when it was awarded","type":"string"},"course_title":{"description":"Course title when it was awarded","type":"string"},"id":{"description":"Badge id","format":"uuid","type":"string"}},"required":["id","certificate_id","course_slug","course_title","awarded_at"],"title":"AdminLearnerBadge","type":"object"},"PathStagesInput":{"description":"Replace the stages of a path","properties":{"stage_ids":{"description":"Distinct existing stages, in order","items":{"description":"Stage id","format":"uuid","type":"string"},"type":"array"}},"required":["stage_ids"],"title":"PathStagesInput","type":"object"},"CourseInput":{"description":"Create or update a course (updates send only what changes)","properties":{"slug":{"description":"Lowercase letters, digits and single hyphens","pattern":"^[a-z0-9]+(-[a-z0-9]+)*$","type":"string"},"status":{"description":"`draft` is visible only to admins; `published` is public","enum":["draft","published"],"type":"string"},"summary":{"description":"Description","type":"string"},"title":{"description":"Title","type":"string"}},"required":["slug","title"],"title":"CourseInput","type":"object"},"Conflict":{"additionalProperties":false,"description":"409 response: someone saved a newer version first (optimistic locking)","properties":{"current_version":{"description":"The version the item has now, to reload and retry against","nullable":true,"type":"integer"},"errors":{"properties":{"base":{"items":{"type":"string"},"type":"array"}},"type":"object"}},"required":["errors","current_version"],"title":"Conflict","type":"object"},"AdminCourseSummary":{"additionalProperties":false,"description":"A course, drafts included","properties":{"id":{"description":"Course id","format":"uuid","type":"string"},"slug":{"description":"Course slug","type":"string"},"status":{"description":"`draft` is visible only to admins; `published` is public","enum":["draft","published"],"type":"string"},"summary":{"description":"Description","type":"string"},"title":{"description":"Course title","type":"string"}},"required":["id","status","title","summary","slug"],"title":"AdminCourseSummary","type":"object"},"Cheatsheet":{"additionalProperties":false,"description":"A course's published quick-reference sheet","properties":{"body":{"description":"Cheatsheet text, Markdown","type":"string"},"course":{"additionalProperties":false,"description":"The course a thing belongs to","properties":{"slug":{"description":"Course slug","type":"string"},"title":{"description":"Course title","type":"string"}},"required":["title","slug"],"type":"object"},"title":{"description":"Cheatsheet title","type":"string"},"updated_at":{"description":"Last edit","format":"date-time","type":"string"}},"required":["title","body","course","updated_at"],"title":"Cheatsheet","type":"object"},"ValidationErrors":{"additionalProperties":false,"description":"422 response: the request was understood but its values were not acceptable","properties":{"errors":{"additionalProperties":{"items":{"type":"string"},"type":"array"},"description":"Field name -> messages, e.g. `{\"slug\": [\"has already been taken\"]}`","type":"object"}},"required":["errors"],"title":"ValidationErrors","type":"object"},"PathInput":{"description":"Create or update a learning path","properties":{"slug":{"description":"Lowercase letters, digits and single hyphens","pattern":"^[a-z0-9]+(-[a-z0-9]+)*$","type":"string"},"status":{"description":"`draft` is visible only to admins; `published` is public","enum":["draft","published"],"type":"string"},"summary":{"description":"Description","type":"string"},"title":{"description":"Title","type":"string"}},"required":["slug","title"],"title":"PathInput","type":"object"},"AdminLessonOutline":{"additionalProperties":false,"properties":{"id":{"description":"Lesson id","format":"uuid","type":"string"},"module_id":{"description":"Module id, or null for a lesson outside any module","format":"uuid","nullable":true,"type":"string"},"position":{"description":"Order within the stage","type":"integer"},"slug":{"description":"Lesson slug","type":"string"},"status":{"description":"`draft` is visible only to admins; `published` is public","enum":["draft","published"],"type":"string"},"summary":{"description":"One-line description","type":"string"},"title":{"description":"Lesson title","type":"string"}},"required":["id","position","status","title","summary","module_id","slug"],"title":"AdminLessonOutline","type":"object"},"CourseBundle":{"description":"A whole course as one JSON file: what `GET /api/admin/courses/{id}/export` returns and `POST /api/admin/import` accepts. Images are not included: a lesson that points at an uploaded image keeps pointing at the same path.","properties":{"cheatsheet":{"$ref":"#/components/schemas/BundleCheatsheet"},"format":{"description":"Identifies the file format and its version","enum":["mycelium-course/1"],"type":"string"},"slug":{"description":"Lowercase letters, digits and single hyphens","pattern":"^[a-z0-9]+(-[a-z0-9]+)*$","type":"string"},"stages":{"description":"At least one; in level order","items":{"$ref":"#/components/schemas/BundleStage"},"type":"array"},"status":{"description":"`draft` is visible only to admins; `published` is public","enum":["draft","published"],"type":"string"},"summary":{"description":"Description","type":"string"},"title":{"description":"Course title","type":"string"}},"required":["format","slug","title","stages"],"title":"CourseBundle","type":"object"},"AdminLearner":{"additionalProperties":false,"description":"What an admin can reset for one learner. Personal data.","properties":{"badges":{"items":{"$ref":"#/components/schemas/AdminLearnerBadge"},"type":"array"},"certificates":{"description":"Including those of reset badges","items":{"$ref":"#/components/schemas/AdminLearnerCertificate"},"type":"array"},"courses":{"description":"Courses they have progress in","items":{"$ref":"#/components/schemas/AdminLearnerCourse"},"type":"array"},"email":{"description":"As last seen in their token","nullable":true,"type":"string"},"id":{"description":"The learner's Keycloak subject","type":"string"},"name":{"description":"As last seen in their token","nullable":true,"type":"string"}},"required":["id","name","badges","courses","certificates","email"],"title":"AdminLearner","type":"object"},"BundleLesson":{"description":"A lesson in a course file","properties":{"body":{"description":"Lesson text, Markdown","type":"string"},"slug":{"description":"Lowercase letters, digits and single hyphens","pattern":"^[a-z0-9]+(-[a-z0-9]+)*$","type":"string"},"status":{"description":"`draft` is visible only to admins; `published` is public","enum":["draft","published"],"type":"string"},"summary":{"description":"One-line description","type":"string"},"title":{"description":"Lesson title","type":"string"}},"required":["slug","title"],"title":"BundleLesson","type":"object"},"Progress":{"additionalProperties":false,"description":"Everything the learner has done","properties":{"completed_lesson_ids":{"description":"Every completed lesson","items":{"description":"Lesson id","format":"uuid","type":"string"},"type":"array"},"stages":{"description":"Stages the learner has started","items":{"$ref":"#/components/schemas/StageProgress"},"type":"array"}},"required":["stages","completed_lesson_ids"],"title":"Progress","type":"object"},"CheatsheetRef":{"additionalProperties":false,"description":"What a course says about its published cheatsheet","properties":{"title":{"description":"Cheatsheet title","type":"string"},"updated_at":{"description":"Last edit","format":"date-time","type":"string"}},"required":["title","updated_at"],"title":"CheatsheetRef","type":"object"},"StageInput":{"description":"Create or update a stage","properties":{"level":{"description":"Stage level, in progression order","enum":["beginner","intermediate","advanced"],"type":"string"},"status":{"description":"`draft` is visible only to admins; `published` is public","enum":["draft","published"],"type":"string"},"summary":{"description":"Description","type":"string"},"title":{"description":"Title","type":"string"}},"required":["level","title"],"title":"StageInput","type":"object"},"Badge":{"additionalProperties":false,"description":"A badge, earned by finishing every stage of a course. Never taken back.","properties":{"awarded_at":{"description":"When it was earned","format":"date-time","type":"string"},"certificate_id":{"description":"The certificate issued for it, once issued","format":"uuid","nullable":true,"type":"string"},"course_slug":{"description":"Course slug when it was earned","type":"string"},"course_title":{"description":"Course title when it was earned","type":"string"},"id":{"description":"Badge id","format":"uuid","type":"string"},"kind":{"description":"What it was earned for","enum":["course_completed"],"type":"string"}},"required":["id","kind","certificate_id","course_slug","course_title","awarded_at"],"title":"Badge","type":"object"},"PathDetail":{"additionalProperties":false,"description":"A learning path with its stages","properties":{"slug":{"description":"Path slug","type":"string"},"stages":{"description":"Stages in order, across courses","items":{"additionalProperties":false,"properties":{"course":{"additionalProperties":false,"description":"The course a thing belongs to","properties":{"slug":{"description":"Course slug","type":"string"},"title":{"description":"Course title","type":"string"}},"required":["title","slug"],"type":"object"},"id":{"description":"Stage id","format":"uuid","type":"string"},"level":{"description":"Stage level, in progression order","enum":["beginner","intermediate","advanced"],"type":"string"},"summary":{"description":"What the stage is about","type":"string"},"title":{"description":"Stage title","type":"string"}},"required":["id","level","title","summary","course"],"type":"object"},"type":"array"},"summary":{"description":"What the path is for","type":"string"},"title":{"description":"Path title","type":"string"}},"required":["title","summary","stages","slug"],"title":"PathDetail","type":"object"},"OrderInput":{"description":"Reorder a stage's modules or lessons","properties":{"ids":{"description":"Every item of the stage exactly once, in the new order","items":{"description":"Id","format":"uuid","type":"string"},"type":"array"}},"required":["ids"],"title":"OrderInput","type":"object"},"Lesson":{"additionalProperties":false,"description":"A published lesson with its text and neighbours","properties":{"body":{"description":"Lesson text, Markdown","type":"string"},"course":{"additionalProperties":false,"description":"The course a thing belongs to","properties":{"slug":{"description":"Course slug","type":"string"},"title":{"description":"Course title","type":"string"}},"required":["title","slug"],"type":"object"},"id":{"description":"Lesson id","format":"uuid","type":"string"},"next":{"allOf":[{"$ref":"#/components/schemas/Neighbour"}],"nullable":true},"position":{"description":"Order within the stage, from 1","type":"integer"},"previous":{"allOf":[{"$ref":"#/components/schemas/Neighbour"}],"nullable":true},"slug":{"description":"Lesson slug, unique within its stage","type":"string"},"stage":{"additionalProperties":false,"properties":{"level":{"description":"Stage level, in progression order","enum":["beginner","intermediate","advanced"],"type":"string"},"title":{"description":"Stage title","type":"string"}},"required":["level","title"],"type":"object"},"summary":{"description":"One-line description","type":"string"},"title":{"description":"Lesson title","type":"string"}},"required":["id","position","next","title","body","summary","stage","course","previous","slug"],"title":"Lesson","type":"object"},"BundleStage":{"description":"A stage in a course file; each level may appear once","properties":{"lessons":{"description":"Lessons that belong to no module (omit when none)","items":{"$ref":"#/components/schemas/BundleLesson"},"type":"array"},"level":{"description":"Stage level, in progression order","enum":["beginner","intermediate","advanced"],"type":"string"},"modules":{"description":"In reading order","items":{"$ref":"#/components/schemas/BundleModule"},"type":"array"},"status":{"description":"`draft` is visible only to admins; `published` is public","enum":["draft","published"],"type":"string"},"summary":{"description":"What the stage is about","type":"string"},"title":{"description":"Stage title","type":"string"}},"required":["level","title"],"title":"BundleStage","type":"object"},"PrerequisiteInput":{"description":"Add a cross-course requirement","properties":{"required_stage_id":{"description":"The stage to require first","format":"uuid","type":"string"}},"required":["required_stage_id"],"title":"PrerequisiteInput","type":"object"},"ImportResult":{"additionalProperties":false,"description":"What an import did (or, for a dry run, would do)","properties":{"counts":{"additionalProperties":false,"properties":{"cheatsheet":{"description":"Whether the file has a cheatsheet","type":"boolean"},"lessons":{"description":"Lessons in the file","type":"integer"},"modules":{"description":"Modules in the file","type":"integer"},"stages":{"description":"Stages in the file","type":"integer"},"words":{"description":"Words of lesson text","type":"integer"}},"required":["modules","words","cheatsheet","stages","lessons"],"type":"object"},"course":{"additionalProperties":false,"properties":{"exists":{"description":"Whether a course with this slug was already there","type":"boolean"},"id":{"description":"The course id once imported","format":"uuid","nullable":true,"type":"string"},"slug":{"description":"Course slug","type":"string"},"title":{"description":"Course title","type":"string"}},"required":["id","title","exists","slug"],"type":"object"},"dry_run":{"description":"True when nothing was written","type":"boolean"},"imported":{"description":"True when the course was written","type":"boolean"},"warnings":{"description":"Worth reading before you confirm","items":{"description":"A thing to know before importing","type":"string"},"type":"array"}},"required":["imported","warnings","course","dry_run","counts"],"title":"ImportResult","type":"object"},"CourseResetResult":{"additionalProperties":false,"description":"What a course progress reset removed","properties":{"lessons_reset":{"description":"Lesson completions removed","type":"integer"},"stages_reset":{"description":"Stage enrollments removed","type":"integer"}},"required":["lessons_reset","stages_reset"],"title":"CourseResetResult","type":"object"},"StageDetail":{"additionalProperties":false,"description":"A published stage with its outline","properties":{"course":{"additionalProperties":false,"description":"The course a thing belongs to","properties":{"slug":{"description":"Course slug","type":"string"},"title":{"description":"Course title","type":"string"}},"required":["title","slug"],"type":"object"},"id":{"description":"Stage id","format":"uuid","type":"string"},"level":{"description":"Stage level, in progression order","enum":["beginner","intermediate","advanced"],"type":"string"},"modules":{"items":{"$ref":"#/components/schemas/ModuleOutline"},"type":"array"},"prerequisites":{"description":"Stages recommended first","items":{"additionalProperties":false,"properties":{"course":{"description":"Course slug","type":"string"},"level":{"description":"Stage level, in progression order","enum":["beginner","intermediate","advanced"],"type":"string"},"title":{"description":"Stage title","type":"string"}},"required":["level","title","course"],"type":"object"},"type":"array"},"summary":{"description":"What the stage is about","type":"string"},"title":{"description":"Stage title","type":"string"},"ungrouped_lessons":{"description":"Lessons that belong to no module","items":{"$ref":"#/components/schemas/LessonOutline"},"type":"array"}},"required":["id","level","modules","title","summary","course","prerequisites","ungrouped_lessons"],"title":"StageDetail","type":"object"},"Course":{"additionalProperties":false,"description":"A published course with its stages","properties":{"cheatsheet":{"allOf":[{"$ref":"#/components/schemas/CheatsheetRef"}],"nullable":true},"levels":{"description":"Levels that have a published stage (only when the stages are loaded)","items":{"description":"Stage level, in progression order","enum":["beginner","intermediate","advanced"],"type":"string"},"type":"array"},"slug":{"description":"Course slug, used in URLs","type":"string"},"stages":{"description":"Published stages, beginner first","items":{"$ref":"#/components/schemas/StageSummary"},"type":"array"},"summary":{"description":"One-paragraph description","type":"string"},"title":{"description":"Course title","type":"string"}},"required":["title","levels","summary","cheatsheet","stages","slug"],"title":"Course","type":"object"},"AnalyticsLesson":{"additionalProperties":false,"properties":{"completions":{"description":"Learners who completed it, ever","type":"integer"},"completions_in_range":{"description":"Completions within the range","type":"integer"},"drop_off":{"description":"Share of the previous lesson's completions lost here, 0 to 1 (null for the first)","nullable":true,"type":"number"},"share_of_enrolled":{"description":"Completions / learners who started the stage (null if nobody did)","nullable":true,"type":"number"},"slug":{"description":"Lesson slug","type":"string"},"title":{"description":"Lesson title","type":"string"}},"required":["title","slug","completions","completions_in_range","drop_off","share_of_enrolled"],"title":"AnalyticsLesson","type":"object"},"Analytics":{"additionalProperties":false,"description":"Counts, a time series and per-stage funnels","properties":{"range":{"description":"The range these numbers cover","enum":["7","30","90","all"],"type":"string"},"series":{"additionalProperties":false,"properties":{"bucket":{"description":"Weekly for long ranges","enum":["day","week"],"type":"string"},"points":{"description":"One per bucket, quiet ones included","items":{"$ref":"#/components/schemas/AnalyticsPoint"},"type":"array"}},"required":["points","bucket"],"type":"object"},"stages":{"items":{"$ref":"#/components/schemas/AnalyticsStage"},"type":"array"},"totals":{"$ref":"#/components/schemas/AnalyticsTotals"}},"required":["range","stages","series","totals"],"title":"Analytics","type":"object"},"AnalyticsStage":{"additionalProperties":false,"description":"Progress through one published stage","properties":{"completed":{"description":"Learners who finished it, ever","type":"integer"},"completed_in_range":{"description":"Finished within the range","type":"integer"},"completion_rate":{"description":"completed / enrolled (null if nobody started)","nullable":true,"type":"number"},"course_slug":{"description":"Course slug","type":"string"},"course_title":{"description":"Course title","type":"string"},"enrolled":{"description":"Learners who started it, ever","type":"integer"},"enrolled_in_range":{"description":"Started within the range","type":"integer"},"lessons":{"description":"In reading order","items":{"$ref":"#/components/schemas/AnalyticsLesson"},"type":"array"},"level":{"description":"Stage level, in progression order","enum":["beginner","intermediate","advanced"],"type":"string"},"title":{"description":"Stage title","type":"string"}},"required":["level","title","completed","lessons","course_slug","course_title","completed_in_range","completion_rate","enrolled","enrolled_in_range"],"title":"AnalyticsStage","type":"object"},"AnalyticsPoint":{"additionalProperties":false,"properties":{"completions":{"description":"Lessons completed","type":"integer"},"date":{"description":"The day, or the Monday of the week","format":"date","type":"string"},"new_learners":{"description":"Learners first seen","type":"integer"}},"required":["date","new_learners","completions"],"title":"AnalyticsPoint","type":"object"},"BookStage":{"additionalProperties":false,"properties":{"id":{"description":"Stage id","format":"uuid","type":"string"},"level":{"description":"Stage level, in progression order","enum":["beginner","intermediate","advanced"],"type":"string"},"modules":{"items":{"$ref":"#/components/schemas/BookModule"},"type":"array"},"summary":{"description":"What the stage is about","type":"string"},"title":{"description":"Stage title","type":"string"},"ungrouped_lessons":{"items":{"$ref":"#/components/schemas/BookLesson"},"type":"array"}},"required":["id","level","modules","title","summary","ungrouped_lessons"],"title":"BookStage","type":"object"},"CompletionResult":{"additionalProperties":false,"description":"Stage progress after (un)completing a lesson","properties":{"completed_at":{"description":"When every lesson was completed","format":"date-time","nullable":true,"type":"string"},"completed_lessons":{"description":"Published lessons completed","type":"integer"},"enrolled_at":{"description":"When the learner started the stage","format":"date-time","nullable":true,"type":"string"},"new_badges":{"description":"Badges this change earned (usually none)","items":{"$ref":"#/components/schemas/Badge"},"type":"array"},"stage_id":{"description":"Stage id","format":"uuid","type":"string"},"total_lessons":{"description":"Published lessons in the stage","type":"integer"}},"required":["stage_id","total_lessons","completed_at","completed_lessons","enrolled_at","new_badges"],"title":"CompletionResult","type":"object"},"CheatsheetInput":{"description":"Create or update a course's cheatsheet","properties":{"body":{"description":"Text, Markdown","type":"string"},"expected_version":{"description":"The `version` you loaded. A stale save is refused with 409.","type":"integer"},"status":{"description":"`draft` is visible only to admins; `published` is public","enum":["draft","published"],"type":"string"},"title":{"description":"Title","maxLength":120,"type":"string"}},"required":["title"],"title":"CheatsheetInput","type":"object"},"Upload":{"additionalProperties":false,"description":"A stored image","properties":{"url":{"description":"Where the image is served from","type":"string"}},"required":["url"],"title":"Upload","type":"object"},"AdminLearnerCourse":{"additionalProperties":false,"description":"A learner's progress in one course","properties":{"course_id":{"description":"Course id","format":"uuid","type":"string"},"lessons_completed":{"description":"Published lessons they completed","type":"integer"},"slug":{"description":"Course slug","type":"string"},"title":{"description":"Course title","type":"string"},"total_lessons":{"description":"Published lessons in the course","type":"integer"}},"required":["title","course_id","slug","lessons_completed","total_lessons"],"title":"AdminLearnerCourse","type":"object"},"AdminCheatsheet":{"additionalProperties":false,"description":"A course's cheatsheet, for editing","properties":{"body":{"description":"Text, Markdown","type":"string"},"course_id":{"description":"Course id","format":"uuid","type":"string"},"id":{"description":"Cheatsheet id","format":"uuid","type":"string"},"status":{"description":"`draft` is visible only to admins; `published` is public","enum":["draft","published"],"type":"string"},"title":{"description":"Title","type":"string"},"updated_at":{"description":"Last edit","format":"date-time","type":"string"},"version":{"description":"Send back as `expected_version` to detect edit conflicts","type":"integer"}},"required":["id","status","version","title","body","course_id","updated_at"],"title":"AdminCheatsheet","type":"object"},"AdminPathTree":{"additionalProperties":false,"description":"A path with its stages","properties":{"id":{"description":"Path id","format":"uuid","type":"string"},"slug":{"description":"Path slug","type":"string"},"stages":{"description":"In order","items":{"$ref":"#/components/schemas/AdminStageWithCourse"},"type":"array"},"status":{"description":"`draft` is visible only to admins; `published` is public","enum":["draft","published"],"type":"string"},"summary":{"description":"What the path is for","type":"string"},"title":{"description":"Path title","type":"string"}},"required":["id","status","title","summary","stages","slug"],"title":"AdminPathTree","type":"object"},"Path":{"additionalProperties":false,"description":"A learning path","properties":{"slug":{"description":"Path slug","type":"string"},"summary":{"description":"What the path is for","type":"string"},"title":{"description":"Path title","type":"string"}},"required":["title","summary","slug"],"title":"Path","type":"object"},"AdminCourse":{"additionalProperties":false,"description":"A course with every stage, module and lesson, drafts included","properties":{"cheatsheet":{"additionalProperties":false,"nullable":true,"properties":{"status":{"description":"`draft` is visible only to admins; `published` is public","enum":["draft","published"],"type":"string"},"title":{"description":"Cheatsheet title","type":"string"}},"required":["status","title"],"type":"object"},"id":{"description":"Course id","format":"uuid","type":"string"},"slug":{"description":"Course slug","type":"string"},"stages":{"items":{"$ref":"#/components/schemas/AdminStageTree"},"type":"array"},"status":{"description":"`draft` is visible only to admins; `published` is public","enum":["draft","published"],"type":"string"},"summary":{"description":"Description","type":"string"},"title":{"description":"Course title","type":"string"},"verification":{"additionalProperties":true,"description":"Source-verification report (empty when the course has none); see Backend.Catalog.Bundle","type":"object"}},"required":["id","status","title","summary","verification","cheatsheet","stages","slug"],"title":"AdminCourse","type":"object"}},"securitySchemes":{"bearerAuth":{"bearerFormat":"JWT","description":"A Keycloak access token for the `mycelium` realm.","scheme":"bearer","type":"http"}}},"info":{"description":"The Mycelium learning platform's API: the course catalog the sites read, a signed-in\nlearner's progress, badges and certificates, and the authoring and analytics API for admins.\n\n**Responses** wrap their payload as `{\"data\": ...}`. **Failures** are `{\"errors\": ...}`:\n`401` no or invalid token, `403` not an admin, `404` not found, `409` an edit conflict,\n`422` values that were not acceptable.\n\n## Signing in to try the protected endpoints\n\nProtected endpoints take a Keycloak **access token** (a JWT) as `Authorization: Bearer <token>`.\nPress **Authorize** and paste the token (without `Bearer `).\n\nFor development: sign in on the learning site, then open `/api/auth/session` on that site\n(for example <http://localhost:3000/api/auth/session>) and copy the `accessToken` value.\nAccess tokens last five minutes; fetch a fresh one when calls start returning `401`.\nThe `/api/admin/*` endpoints also need the `admin` realm role.\n\n## Conventions\n\n- Ids are UUIDs. Courses, lessons and paths also have URL-friendly **slugs**.\n- Lesson and cheatsheet bodies are **Markdown**.\n- `draft` content is visible only through the admin endpoints; the public catalog shows `published` only.\n- Editing lessons and cheatsheets uses **optimistic locking**: send back the `version` you loaded\n  as `expected_version`; a newer save by someone else answers `409`.\n","title":"Mycelium API","version":"0.1.0"},"openapi":"3.0.3","paths":{"/api/courses/{slug}":{"get":{"callbacks":{},"deprecated":false,"operationId":"getCourse","parameters":[{"description":"Course slug","in":"path","name":"slug","required":true,"schema":{"description":"Course slug","type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"additionalProperties":false,"properties":{"data":{"$ref":"#/components/schemas/Course"}},"required":["data"],"type":"object"}}},"description":"The course"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Not found (or not published)"}},"security":[],"summary":"A course with its stages","tags":["Catalog"]}},"/api/admin/courses/{course_id}/stages":{"post":{"callbacks":{},"deprecated":false,"operationId":"adminCreateStage","parameters":[{"description":"Course id","in":"path","name":"course_id","required":true,"schema":{"description":"Course id","format":"uuid","type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StageInput"}}},"description":"The new stage","required":true},"responses":{"201":{"content":{"application/json":{"schema":{"additionalProperties":false,"properties":{"data":{"$ref":"#/components/schemas/AdminStage"}},"required":["data"],"type":"object"}}},"description":"The stage"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Missing, malformed or expired access token"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"The token does not carry the `admin` role"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Not found (or not published)"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationErrors"}}},"description":"The values were not acceptable"}},"security":[{"bearerAuth":[]}],"summary":"Add a stage to a course","tags":["Admin: courses"]}},"/api/courses/{slug}/cheatsheet":{"get":{"callbacks":{},"deprecated":false,"description":"The body is Markdown. The site also renders it as a PDF.","operationId":"getCheatsheet","parameters":[{"description":"Course slug","in":"path","name":"slug","required":true,"schema":{"description":"Course slug","type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"additionalProperties":false,"properties":{"data":{"$ref":"#/components/schemas/Cheatsheet"}},"required":["data"],"type":"object"}}},"description":"The cheatsheet"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Not found (or not published)"}},"security":[],"summary":"A course's published cheatsheet","tags":["Catalog"]}},"/api/admin/uploads":{"post":{"callbacks":{},"deprecated":false,"description":"For use in lesson Markdown. The returned `url` is served by this API.","operationId":"adminUploadImage","parameters":[],"requestBody":{"content":{"multipart/form-data":{"schema":{"properties":{"file":{"description":"PNG, JPEG, GIF, WebP or SVG","format":"binary","type":"string"}},"required":["file"],"type":"object"}}},"description":"The image","required":true},"responses":{"201":{"content":{"application/json":{"schema":{"additionalProperties":false,"properties":{"data":{"$ref":"#/components/schemas/Upload"}},"required":["data"],"type":"object"}}},"description":"Stored"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Missing, malformed or expired access token"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"The token does not carry the `admin` role"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationErrors"}}},"description":"The values were not acceptable"}},"security":[{"bearerAuth":[]}],"summary":"Upload an image","tags":["Admin: uploads"]}},"/api/me/lessons/{lesson_id}/completion":{"delete":{"callbacks":{},"deprecated":false,"description":"Idempotent. Reopens the stage if it was finished. Badges already earned are kept.","operationId":"uncompleteLesson","parameters":[{"description":"Lesson id","in":"path","name":"lesson_id","required":true,"schema":{"description":"Lesson id","format":"uuid","type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"additionalProperties":false,"properties":{"data":{"$ref":"#/components/schemas/CompletionResult"}},"required":["data"],"type":"object"}}},"description":"The stage's progress"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Missing, malformed or expired access token"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Not found (or not published)"}},"security":[{"bearerAuth":[]}],"summary":"Mark a lesson not complete","tags":["Learner"]},"put":{"callbacks":{},"deprecated":false,"description":"Idempotent. Completing the last lesson of a stage finishes it, and finishing every stage of a course earns its badge (returned in `new_badges`).","operationId":"completeLesson","parameters":[{"description":"Lesson id","in":"path","name":"lesson_id","required":true,"schema":{"description":"Lesson id","format":"uuid","type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"additionalProperties":false,"properties":{"data":{"$ref":"#/components/schemas/CompletionResult"}},"required":["data"],"type":"object"}}},"description":"The stage's progress, plus any badges earned"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Missing, malformed or expired access token"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Not found (or not published)"}},"security":[{"bearerAuth":[]}],"summary":"Mark a lesson complete","tags":["Learner"]}},"/api/admin/stages/{stage_id}/lessons/order":{"put":{"callbacks":{},"deprecated":false,"operationId":"adminReorderLessons","parameters":[{"description":"Stage id","in":"path","name":"stage_id","required":true,"schema":{"description":"Stage id","format":"uuid","type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderInput"}}},"description":"Every lesson id of the stage, once, in the new order","required":true},"responses":{"204":{"description":"Reordered"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Missing, malformed or expired access token"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"The token does not carry the `admin` role"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Not found (or not published)"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationErrors"}}},"description":"The values were not acceptable"}},"security":[{"bearerAuth":[]}],"summary":"Reorder a stage's lessons","tags":["Admin: lessons"]}},"/api/paths/{slug}":{"get":{"callbacks":{},"deprecated":false,"operationId":"getPath","parameters":[{"description":"Path slug","in":"path","name":"slug","required":true,"schema":{"description":"Path slug","type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"additionalProperties":false,"properties":{"data":{"$ref":"#/components/schemas/PathDetail"}},"required":["data"],"type":"object"}}},"description":"The path"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Not found (or not published)"}},"security":[],"summary":"A learning path with its stages","tags":["Catalog"]}},"/api/admin/courses/{id}/export":{"get":{"callbacks":{},"deprecated":false,"description":"The whole course (drafts included, every lesson's Markdown inline) as one JSON file, served as a download (`Content-Disposition: attachment; filename=\"<slug>.course.json\"`). Import it again with `POST /api/admin/import`.","operationId":"adminExportCourse","parameters":[{"description":"Course id","in":"path","name":"id","required":true,"schema":{"description":"Course id","format":"uuid","type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CourseBundle"}}},"description":"The course file"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Missing, malformed or expired access token"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"The token does not carry the `admin` role"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Not found (or not published)"}},"security":[{"bearerAuth":[]}],"summary":"Export a course as a file","tags":["Admin: import and export"]}},"/api/admin/stages/{stage_id}/modules/order":{"put":{"callbacks":{},"deprecated":false,"operationId":"adminReorderModules","parameters":[{"description":"Stage id","in":"path","name":"stage_id","required":true,"schema":{"description":"Stage id","format":"uuid","type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderInput"}}},"description":"Every module id of the stage, once, in the new order","required":true},"responses":{"204":{"description":"Reordered"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Missing, malformed or expired access token"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"The token does not carry the `admin` role"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Not found (or not published)"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationErrors"}}},"description":"The values were not acceptable"}},"security":[{"bearerAuth":[]}],"summary":"Reorder a stage's modules","tags":["Admin: courses"]}},"/api/admin/stages/{stage_id}/modules":{"post":{"callbacks":{},"deprecated":false,"operationId":"adminCreateModule","parameters":[{"description":"Stage id","in":"path","name":"stage_id","required":true,"schema":{"description":"Stage id","format":"uuid","type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ModuleInput"}}},"description":"The new module","required":true},"responses":{"201":{"content":{"application/json":{"schema":{"additionalProperties":false,"properties":{"data":{"$ref":"#/components/schemas/AdminModule"}},"required":["data"],"type":"object"}}},"description":"The module"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Missing, malformed or expired access token"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"The token does not carry the `admin` role"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Not found (or not published)"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationErrors"}}},"description":"The values were not acceptable"}},"security":[{"bearerAuth":[]}],"summary":"Add a module to a stage","tags":["Admin: courses"]}},"/api/admin/paths":{"get":{"callbacks":{},"deprecated":false,"operationId":"adminListPaths","parameters":[],"responses":{"200":{"content":{"application/json":{"schema":{"additionalProperties":false,"properties":{"data":{"items":{"$ref":"#/components/schemas/AdminPathListItem"},"type":"array"}},"required":["data"],"type":"object"}}},"description":"Paths with their stage counts"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Missing, malformed or expired access token"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"The token does not carry the `admin` role"}},"security":[{"bearerAuth":[]}],"summary":"All learning paths, drafts included","tags":["Admin: paths"]},"post":{"callbacks":{},"deprecated":false,"operationId":"adminCreatePath","parameters":[],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PathInput"}}},"description":"The new path","required":true},"responses":{"201":{"content":{"application/json":{"schema":{"additionalProperties":false,"properties":{"data":{"$ref":"#/components/schemas/AdminPathTree"}},"required":["data"],"type":"object"}}},"description":"The path (no stages yet)"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Missing, malformed or expired access token"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"The token does not carry the `admin` role"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationErrors"}}},"description":"The values were not acceptable"}},"security":[{"bearerAuth":[]}],"summary":"Create a learning path","tags":["Admin: paths"]}},"/api/courses/{slug}/stages/{level}":{"get":{"callbacks":{},"deprecated":false,"operationId":"getStage","parameters":[{"description":"Course slug","in":"path","name":"slug","required":true,"schema":{"description":"Course slug","type":"string"}},{"description":"Stage level","in":"path","name":"level","required":true,"schema":{"enum":["beginner","intermediate","advanced"],"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"additionalProperties":false,"properties":{"data":{"$ref":"#/components/schemas/StageDetail"}},"required":["data"],"type":"object"}}},"description":"The stage: modules and lessons without their text"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Not found (or not published)"}},"security":[],"summary":"A stage with its outline","tags":["Catalog"]}},"/api/admin/learners/{user_id}/certificates/{certificate_id}/revocation":{"post":{"callbacks":{},"deprecated":false,"description":"Idempotent. The certificate stays; verification shows it as revoked. The badge is kept.","operationId":"revokeCertificate","parameters":[{"description":"The learner's Keycloak subject","in":"path","name":"user_id","required":true,"schema":{"description":"The learner's Keycloak subject","type":"string"}},{"description":"Certificate id","in":"path","name":"certificate_id","required":true,"schema":{"description":"Certificate id","format":"uuid","type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"additionalProperties":false,"properties":{"data":{"$ref":"#/components/schemas/CertificateRevocationResult"}},"required":["data"],"type":"object"}}},"description":"Revoked"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Missing, malformed or expired access token"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"The token does not carry the `admin` role"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Not found (or not published)"}},"security":[{"bearerAuth":[]}],"summary":"Revoke a learner's certificate","tags":["Admin: learners"]}},"/api/admin/courses/{course_id}/cheatsheet":{"delete":{"callbacks":{},"deprecated":false,"operationId":"adminDeleteCheatsheet","parameters":[{"description":"Course id","in":"path","name":"course_id","required":true,"schema":{"description":"Course id","format":"uuid","type":"string"}}],"responses":{"204":{"description":"Deleted"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Missing, malformed or expired access token"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"The token does not carry the `admin` role"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Not found (or not published)"}},"security":[{"bearerAuth":[]}],"summary":"Delete a course's cheatsheet","tags":["Admin: cheatsheets"]},"get":{"callbacks":{},"deprecated":false,"operationId":"adminGetCheatsheet","parameters":[{"description":"Course id","in":"path","name":"course_id","required":true,"schema":{"description":"Course id","format":"uuid","type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"additionalProperties":false,"properties":{"data":{"$ref":"#/components/schemas/AdminCheatsheet"}},"required":["data"],"type":"object"}}},"description":"The cheatsheet"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Missing, malformed or expired access token"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"The token does not carry the `admin` role"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Not found (or not published)"}},"security":[{"bearerAuth":[]}],"summary":"A course's cheatsheet, drafts included","tags":["Admin: cheatsheets"]},"put":{"callbacks":{},"deprecated":false,"description":"A course has at most one. When updating, send `expected_version`; a stale save is refused with 409.","operationId":"adminSaveCheatsheet","parameters":[{"description":"Course id","in":"path","name":"course_id","required":true,"schema":{"description":"Course id","format":"uuid","type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CheatsheetInput"}}},"description":"The cheatsheet","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"additionalProperties":false,"properties":{"data":{"$ref":"#/components/schemas/AdminCheatsheet"}},"required":["data"],"type":"object"}}},"description":"The cheatsheet"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Missing, malformed or expired access token"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"The token does not carry the `admin` role"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Not found (or not published)"},"409":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Conflict"}}},"description":"Someone saved a newer version first; reload and retry"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationErrors"}}},"description":"The values were not acceptable"}},"security":[{"bearerAuth":[]}],"summary":"Create or update a course's cheatsheet","tags":["Admin: cheatsheets"]}},"/api/courses":{"get":{"callbacks":{},"deprecated":false,"operationId":"listCourses","parameters":[],"responses":{"200":{"content":{"application/json":{"schema":{"additionalProperties":false,"properties":{"data":{"items":{"$ref":"#/components/schemas/CourseSummary"},"type":"array"}},"required":["data"],"type":"object"}}},"description":"Courses, by title"}},"security":[],"summary":"Published courses","tags":["Catalog"]}},"/api/paths":{"get":{"callbacks":{},"deprecated":false,"operationId":"listPaths","parameters":[],"responses":{"200":{"content":{"application/json":{"schema":{"additionalProperties":false,"properties":{"data":{"items":{"$ref":"#/components/schemas/Path"},"type":"array"}},"required":["data"],"type":"object"}}},"description":"Paths"}},"security":[],"summary":"Published learning paths","tags":["Catalog"]}},"/api/admin/courses":{"get":{"callbacks":{},"deprecated":false,"operationId":"adminListCourses","parameters":[],"responses":{"200":{"content":{"application/json":{"schema":{"additionalProperties":false,"properties":{"data":{"items":{"$ref":"#/components/schemas/AdminCourseListItem"},"type":"array"}},"required":["data"],"type":"object"}}},"description":"Courses with their stages"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Missing, malformed or expired access token"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"The token does not carry the `admin` role"}},"security":[{"bearerAuth":[]}],"summary":"All courses, drafts included","tags":["Admin: courses"]},"post":{"callbacks":{},"deprecated":false,"operationId":"adminCreateCourse","parameters":[],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CourseInput"}}},"description":"The new course","required":true},"responses":{"201":{"content":{"application/json":{"schema":{"additionalProperties":false,"properties":{"data":{"$ref":"#/components/schemas/AdminCourseSummary"}},"required":["data"],"type":"object"}}},"description":"The course"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Missing, malformed or expired access token"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"The token does not carry the `admin` role"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationErrors"}}},"description":"The values were not acceptable"}},"security":[{"bearerAuth":[]}],"summary":"Create a course","tags":["Admin: courses"]}},"/api/admin/import":{"post":{"callbacks":{},"deprecated":false,"description":"How a course gets into a fresh installation (nothing is seeded). Send `dry_run: true` first: it validates the file and reports what would happen, with warnings, without writing. Everything is imported in one transaction, so a failure leaves nothing behind. A file that is not valid answers 422 with every problem and where it is (for example `stages[0].lessons[3].slug: must be a lowercase slug`); a course whose slug exists answers 409 unless `mode` is `update`.","operationId":"adminImportCourse","parameters":[],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ImportRequest"}}},"description":"The course file and how to import it","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"additionalProperties":false,"properties":{"data":{"$ref":"#/components/schemas/ImportResult"}},"required":["data"],"type":"object"}}},"description":"What was imported (or would be)"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Missing, malformed or expired access token"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"The token does not carry the `admin` role"},"409":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Conflict"}}},"description":"Someone saved a newer version first; reload and retry"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationErrors"}}},"description":"The values were not acceptable"}},"security":[{"bearerAuth":[]}],"summary":"Import a course from a file","tags":["Admin: import and export"]}},"/api/admin/learners/{user_id}/courses/{course_id}/progress":{"delete":{"callbacks":{},"deprecated":false,"description":"Removes their lesson completions and stage enrollments in the course. Badges and certificates are kept.","operationId":"resetCourseProgress","parameters":[{"description":"The learner's Keycloak subject","in":"path","name":"user_id","required":true,"schema":{"description":"The learner's Keycloak subject","type":"string"}},{"description":"Course id","in":"path","name":"course_id","required":true,"schema":{"description":"Course id","format":"uuid","type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"additionalProperties":false,"properties":{"data":{"$ref":"#/components/schemas/CourseResetResult"}},"required":["data"],"type":"object"}}},"description":"What was removed"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Missing, malformed or expired access token"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"The token does not carry the `admin` role"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Not found (or not published)"}},"security":[{"bearerAuth":[]}],"summary":"Reset a learner's progress in a course","tags":["Admin: learners"]}},"/api/admin/stages/{id}":{"delete":{"callbacks":{},"deprecated":false,"description":"The course's remaining stages are re-linked in order.","operationId":"adminDeleteStage","parameters":[{"description":"Stage id","in":"path","name":"id","required":true,"schema":{"description":"Stage id","format":"uuid","type":"string"}}],"responses":{"204":{"description":"Deleted"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Missing, malformed or expired access token"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"The token does not carry the `admin` role"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Not found (or not published)"}},"security":[{"bearerAuth":[]}],"summary":"Delete a stage","tags":["Admin: courses"]},"patch":{"callbacks":{},"deprecated":false,"operationId":"adminUpdateStage","parameters":[{"description":"Stage id","in":"path","name":"id","required":true,"schema":{"description":"Stage id","format":"uuid","type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StageInput"}}},"description":"The fields to change","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"additionalProperties":false,"properties":{"data":{"$ref":"#/components/schemas/AdminStage"}},"required":["data"],"type":"object"}}},"description":"The stage"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Missing, malformed or expired access token"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"The token does not carry the `admin` role"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Not found (or not published)"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationErrors"}}},"description":"The values were not acceptable"}},"security":[{"bearerAuth":[]}],"summary":"Update a stage","tags":["Admin: courses"]}},"/api/admin/learners/{user_id}/badges/{badge_id}":{"delete":{"callbacks":{},"deprecated":false,"description":"A certificate issued from the badge is revoked, not deleted: its verification link keeps working and says so.","operationId":"resetBadge","parameters":[{"description":"The learner's Keycloak subject","in":"path","name":"user_id","required":true,"schema":{"description":"The learner's Keycloak subject","type":"string"}},{"description":"Badge id","in":"path","name":"badge_id","required":true,"schema":{"description":"Badge id","format":"uuid","type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"additionalProperties":false,"properties":{"data":{"$ref":"#/components/schemas/BadgeResetResult"}},"required":["data"],"type":"object"}}},"description":"What was done"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Missing, malformed or expired access token"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"The token does not carry the `admin` role"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Not found (or not published)"}},"security":[{"bearerAuth":[]}],"summary":"Take away a learner's badge","tags":["Admin: learners"]}},"/api/courses/{slug}/stages/{level}/lessons/{lesson}":{"get":{"callbacks":{},"deprecated":false,"operationId":"getLesson","parameters":[{"description":"Course slug","in":"path","name":"slug","required":true,"schema":{"description":"Course slug","type":"string"}},{"description":"Stage level","in":"path","name":"level","required":true,"schema":{"enum":["beginner","intermediate","advanced"],"type":"string"}},{"description":"Lesson slug","in":"path","name":"lesson","required":true,"schema":{"description":"Lesson slug","type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"additionalProperties":false,"properties":{"data":{"$ref":"#/components/schemas/Lesson"}},"required":["data"],"type":"object"}}},"description":"The lesson, with the previous and next lessons"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Not found (or not published)"}},"security":[],"summary":"A lesson with its text","tags":["Catalog"]}},"/api/openapi":{"get":{"callbacks":{},"deprecated":false,"description":"The OpenAPI document that Swagger UI (at `/`) renders.","operationId":"getOpenApiSpec","parameters":[],"responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"OpenAPI 3 document"}},"security":[],"summary":"This specification","tags":["Documentation"]}},"/api/admin/modules/{id}":{"delete":{"callbacks":{},"deprecated":false,"operationId":"adminDeleteModule","parameters":[{"description":"Module id","in":"path","name":"id","required":true,"schema":{"description":"Module id","format":"uuid","type":"string"}}],"responses":{"204":{"description":"Deleted"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Missing, malformed or expired access token"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"The token does not carry the `admin` role"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Not found (or not published)"}},"security":[{"bearerAuth":[]}],"summary":"Delete a module","tags":["Admin: courses"]},"patch":{"callbacks":{},"deprecated":false,"operationId":"adminUpdateModule","parameters":[{"description":"Module id","in":"path","name":"id","required":true,"schema":{"description":"Module id","format":"uuid","type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ModuleInput"}}},"description":"The fields to change","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"additionalProperties":false,"properties":{"data":{"$ref":"#/components/schemas/AdminModule"}},"required":["data"],"type":"object"}}},"description":"The module"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Missing, malformed or expired access token"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"The token does not carry the `admin` role"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Not found (or not published)"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationErrors"}}},"description":"The values were not acceptable"}},"security":[{"bearerAuth":[]}],"summary":"Update a module","tags":["Admin: courses"]}},"/api/me/badges/{badge_id}/certificate":{"post":{"callbacks":{},"deprecated":false,"description":"A badge has at most one certificate: asking again returns the existing one unchanged (the name cannot be rewritten).","operationId":"issueCertificate","parameters":[{"description":"Badge id","in":"path","name":"badge_id","required":true,"schema":{"description":"Badge id","format":"uuid","type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CertificateInput"}}},"description":"The name to print","required":true},"responses":{"201":{"content":{"application/json":{"schema":{"additionalProperties":false,"properties":{"data":{"$ref":"#/components/schemas/Certificate"}},"required":["data"],"type":"object"}}},"description":"The certificate"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Missing, malformed or expired access token"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Not found (or not published)"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationErrors"}}},"description":"The values were not acceptable"}},"security":[{"bearerAuth":[]}],"summary":"Issue the certificate for one of my badges","tags":["Certificates"]}},"/api/admin/stages/{stage_id}/lessons":{"post":{"callbacks":{},"deprecated":false,"operationId":"adminCreateLesson","parameters":[{"description":"Stage id","in":"path","name":"stage_id","required":true,"schema":{"description":"Stage id","format":"uuid","type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LessonInput"}}},"description":"The new lesson","required":true},"responses":{"201":{"content":{"application/json":{"schema":{"additionalProperties":false,"properties":{"data":{"$ref":"#/components/schemas/AdminLesson"}},"required":["data"],"type":"object"}}},"description":"The lesson"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Missing, malformed or expired access token"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"The token does not carry the `admin` role"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Not found (or not published)"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationErrors"}}},"description":"The values were not acceptable"}},"security":[{"bearerAuth":[]}],"summary":"Add a lesson to a stage","tags":["Admin: lessons"]}},"/api/courses/{slug}/book":{"get":{"callbacks":{},"deprecated":false,"description":"Everything needed to build a PDF or ebook in one request: every published stage, module and lesson with its Markdown body. `updated_at` changes whenever any lesson does, so clients can cache what they build.","operationId":"getBook","parameters":[{"description":"Course slug","in":"path","name":"slug","required":true,"schema":{"description":"Course slug","type":"string"}},{"description":"Only this stage","in":"query","name":"level","required":false,"schema":{"enum":["beginner","intermediate","advanced"],"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"additionalProperties":false,"properties":{"data":{"$ref":"#/components/schemas/Book"}},"required":["data"],"type":"object"}}},"description":"The book"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Not found (or not published)"}},"security":[],"summary":"The course (or one stage) with every lesson","tags":["Catalog"]}},"/api/admin/analytics/learners":{"get":{"callbacks":{},"deprecated":false,"description":"Personal data: the dashboard shows counts only and links here deliberately.","operationId":"listLearners","parameters":[],"responses":{"200":{"content":{"application/json":{"schema":{"additionalProperties":false,"properties":{"data":{"items":{"$ref":"#/components/schemas/AnalyticsLearner"},"type":"array"}},"required":["data"],"type":"object"}}},"description":"Most recently active first"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Missing, malformed or expired access token"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"The token does not carry the `admin` role"}},"security":[{"bearerAuth":[]}],"summary":"Learners, with names and emails","tags":["Admin: analytics"]}},"/api/admin/paths/{id}/stages":{"put":{"callbacks":{},"deprecated":false,"operationId":"adminSetPathStages","parameters":[{"description":"Path id","in":"path","name":"id","required":true,"schema":{"description":"Path id","format":"uuid","type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PathStagesInput"}}},"description":"The stages, in order","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"additionalProperties":false,"properties":{"data":{"$ref":"#/components/schemas/AdminPathTree"}},"required":["data"],"type":"object"}}},"description":"The path"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Missing, malformed or expired access token"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"The token does not carry the `admin` role"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Not found (or not published)"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationErrors"}}},"description":"The values were not acceptable"}},"security":[{"bearerAuth":[]}],"summary":"Replace a path's stages","tags":["Admin: paths"]}},"/api/certificates/{id}":{"get":{"callbacks":{},"deprecated":false,"description":"Anyone holding a certificate's id can confirm who completed what and when. The learner's account id is never returned.","operationId":"verifyCertificate","parameters":[{"description":"Certificate id","in":"path","name":"id","required":true,"schema":{"description":"Certificate id","format":"uuid","type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"additionalProperties":false,"properties":{"data":{"$ref":"#/components/schemas/Certificate"}},"required":["data"],"type":"object"}}},"description":"The certificate"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Not found (or not published)"}},"security":[],"summary":"Verify a certificate","tags":["Certificates"]}},"/api/admin/stages/{stage_id}/prerequisites":{"get":{"callbacks":{},"deprecated":false,"description":"A course's own beginner, intermediate, advanced order is automatic; this manages the extra, cross-course requirements.","operationId":"adminListPrerequisites","parameters":[{"description":"Stage id","in":"path","name":"stage_id","required":true,"schema":{"description":"Stage id","format":"uuid","type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"additionalProperties":false,"properties":{"data":{"$ref":"#/components/schemas/AdminPrerequisites"}},"required":["data"],"type":"object"}}},"description":"Required stages and possible additions"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Missing, malformed or expired access token"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"The token does not carry the `admin` role"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Not found (or not published)"}},"security":[{"bearerAuth":[]}],"summary":"A stage's requirements","tags":["Admin: requirements"]},"post":{"callbacks":{},"deprecated":false,"description":"Refused (422) if it would create a cycle, name the stage itself, or repeat the course's own order.","operationId":"adminAddPrerequisite","parameters":[{"description":"Stage id","in":"path","name":"stage_id","required":true,"schema":{"description":"Stage id","format":"uuid","type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PrerequisiteInput"}}},"description":"The stage to require","required":true},"responses":{"201":{"content":{"application/json":{"schema":{"additionalProperties":false,"properties":{"data":{"$ref":"#/components/schemas/AdminPrerequisites"}},"required":["data"],"type":"object"}}},"description":"The updated requirements"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Missing, malformed or expired access token"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"The token does not carry the `admin` role"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Not found (or not published)"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationErrors"}}},"description":"The values were not acceptable"}},"security":[{"bearerAuth":[]}],"summary":"Require another stage first","tags":["Admin: requirements"]}},"/api/me/progress":{"get":{"callbacks":{},"deprecated":false,"description":"Also records that this learner was just seen (name and email from the token), at most every five minutes.","operationId":"getProgress","parameters":[],"responses":{"200":{"content":{"application/json":{"schema":{"additionalProperties":false,"properties":{"data":{"$ref":"#/components/schemas/Progress"}},"required":["data"],"type":"object"}}},"description":"Completed lessons and a summary per started stage"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Missing, malformed or expired access token"}},"security":[{"bearerAuth":[]}],"summary":"My progress","tags":["Learner"]}},"/api/admin/paths/{id}":{"delete":{"callbacks":{},"deprecated":false,"operationId":"adminDeletePath","parameters":[{"description":"Path id","in":"path","name":"id","required":true,"schema":{"description":"Path id","format":"uuid","type":"string"}}],"responses":{"204":{"description":"Deleted"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Missing, malformed or expired access token"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"The token does not carry the `admin` role"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Not found (or not published)"}},"security":[{"bearerAuth":[]}],"summary":"Delete a learning path","tags":["Admin: paths"]},"get":{"callbacks":{},"deprecated":false,"operationId":"adminGetPath","parameters":[{"description":"Path id","in":"path","name":"id","required":true,"schema":{"description":"Path id","format":"uuid","type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"additionalProperties":false,"properties":{"data":{"$ref":"#/components/schemas/AdminPathTree"}},"required":["data"],"type":"object"}}},"description":"The path"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Missing, malformed or expired access token"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"The token does not carry the `admin` role"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Not found (or not published)"}},"security":[{"bearerAuth":[]}],"summary":"A learning path with its stages","tags":["Admin: paths"]},"patch":{"callbacks":{},"deprecated":false,"operationId":"adminUpdatePath","parameters":[{"description":"Path id","in":"path","name":"id","required":true,"schema":{"description":"Path id","format":"uuid","type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PathInput"}}},"description":"The fields to change","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"additionalProperties":false,"properties":{"data":{"$ref":"#/components/schemas/AdminPathTree"}},"required":["data"],"type":"object"}}},"description":"The path"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Missing, malformed or expired access token"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"The token does not carry the `admin` role"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Not found (or not published)"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationErrors"}}},"description":"The values were not acceptable"}},"security":[{"bearerAuth":[]}],"summary":"Update a learning path","tags":["Admin: paths"]}},"/api/admin/learners/{user_id}":{"get":{"callbacks":{},"deprecated":false,"description":"Personal data. What the admin can reset, in one place.","operationId":"getLearner","parameters":[{"description":"The learner's Keycloak subject","in":"path","name":"user_id","required":true,"schema":{"description":"The learner's Keycloak subject","type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"additionalProperties":false,"properties":{"data":{"$ref":"#/components/schemas/AdminLearner"}},"required":["data"],"type":"object"}}},"description":"The learner"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Missing, malformed or expired access token"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"The token does not carry the `admin` role"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Not found (or not published)"}},"security":[{"bearerAuth":[]}],"summary":"One learner's progress, badges and certificates","tags":["Admin: learners"]}},"/api/admin/courses/{id}":{"delete":{"callbacks":{},"deprecated":false,"description":"Removes its stages, lessons and learners' progress. Badges and certificates already issued are kept.","operationId":"adminDeleteCourse","parameters":[{"description":"Course id","in":"path","name":"id","required":true,"schema":{"description":"Course id","format":"uuid","type":"string"}}],"responses":{"204":{"description":"Deleted"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Missing, malformed or expired access token"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"The token does not carry the `admin` role"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Not found (or not published)"}},"security":[{"bearerAuth":[]}],"summary":"Delete a course","tags":["Admin: courses"]},"get":{"callbacks":{},"deprecated":false,"operationId":"adminGetCourse","parameters":[{"description":"Course id","in":"path","name":"id","required":true,"schema":{"description":"Course id","format":"uuid","type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"additionalProperties":false,"properties":{"data":{"$ref":"#/components/schemas/AdminCourse"}},"required":["data"],"type":"object"}}},"description":"The course tree"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Missing, malformed or expired access token"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"The token does not carry the `admin` role"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Not found (or not published)"}},"security":[{"bearerAuth":[]}],"summary":"A course with every stage, module and lesson","tags":["Admin: courses"]},"patch":{"callbacks":{},"deprecated":false,"operationId":"adminUpdateCourse","parameters":[{"description":"Course id","in":"path","name":"id","required":true,"schema":{"description":"Course id","format":"uuid","type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CourseInput"}}},"description":"The fields to change","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"additionalProperties":false,"properties":{"data":{"$ref":"#/components/schemas/AdminCourseSummary"}},"required":["data"],"type":"object"}}},"description":"The course"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Missing, malformed or expired access token"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"The token does not carry the `admin` role"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Not found (or not published)"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationErrors"}}},"description":"The values were not acceptable"}},"security":[{"bearerAuth":[]}],"summary":"Update a course","tags":["Admin: courses"]}},"/api/me/badges":{"get":{"callbacks":{},"deprecated":false,"operationId":"listBadges","parameters":[],"responses":{"200":{"content":{"application/json":{"schema":{"additionalProperties":false,"properties":{"data":{"items":{"$ref":"#/components/schemas/Badge"},"type":"array"}},"required":["data"],"type":"object"}}},"description":"Badges, newest first"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Missing, malformed or expired access token"}},"security":[{"bearerAuth":[]}],"summary":"My badges","tags":["Learner"]}},"/api/admin/analytics":{"get":{"callbacks":{},"deprecated":false,"description":"Totals and the time series follow `range`. The per-stage and per-lesson numbers are all-time, with an in-range column beside them.","operationId":"getAnalytics","parameters":[{"description":"How far back (default `30`)","in":"query","name":"range","required":false,"schema":{"default":"30","enum":["7","30","90","all"],"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"additionalProperties":false,"properties":{"data":{"$ref":"#/components/schemas/Analytics"}},"required":["data"],"type":"object"}}},"description":"The numbers"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Missing, malformed or expired access token"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"The token does not carry the `admin` role"}},"security":[{"bearerAuth":[]}],"summary":"Dashboard numbers","tags":["Admin: analytics"]}},"/api/admin/lessons/{id}":{"delete":{"callbacks":{},"deprecated":false,"operationId":"adminDeleteLesson","parameters":[{"description":"Lesson id","in":"path","name":"id","required":true,"schema":{"description":"Lesson id","format":"uuid","type":"string"}}],"responses":{"204":{"description":"Deleted"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Missing, malformed or expired access token"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"The token does not carry the `admin` role"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Not found (or not published)"}},"security":[{"bearerAuth":[]}],"summary":"Delete a lesson","tags":["Admin: lessons"]},"get":{"callbacks":{},"deprecated":false,"operationId":"adminGetLesson","parameters":[{"description":"Lesson id","in":"path","name":"id","required":true,"schema":{"description":"Lesson id","format":"uuid","type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"additionalProperties":false,"properties":{"data":{"$ref":"#/components/schemas/AdminLesson"}},"required":["data"],"type":"object"}}},"description":"The lesson"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Missing, malformed or expired access token"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"The token does not carry the `admin` role"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Not found (or not published)"}},"security":[{"bearerAuth":[]}],"summary":"A lesson with its text","tags":["Admin: lessons"]},"patch":{"callbacks":{},"deprecated":false,"description":"Send `expected_version` (the `version` you loaded). If someone saved in between, the update is refused with 409 and the current version.","operationId":"adminUpdateLesson","parameters":[{"description":"Lesson id","in":"path","name":"id","required":true,"schema":{"description":"Lesson id","format":"uuid","type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LessonInput"}}},"description":"The fields to change","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"additionalProperties":false,"properties":{"data":{"$ref":"#/components/schemas/AdminLesson"}},"required":["data"],"type":"object"}}},"description":"The lesson"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Missing, malformed or expired access token"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"The token does not carry the `admin` role"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Not found (or not published)"},"409":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Conflict"}}},"description":"Someone saved a newer version first; reload and retry"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationErrors"}}},"description":"The values were not acceptable"}},"security":[{"bearerAuth":[]}],"summary":"Update a lesson","tags":["Admin: lessons"]}},"/api/admin/stages/{stage_id}/prerequisites/{required_id}":{"delete":{"callbacks":{},"deprecated":false,"description":"A course's own automatic requirements cannot be removed (422).","operationId":"adminRemovePrerequisite","parameters":[{"description":"Stage id","in":"path","name":"stage_id","required":true,"schema":{"description":"Stage id","format":"uuid","type":"string"}},{"description":"The required stage's id","in":"path","name":"required_id","required":true,"schema":{"description":"The required stage's id","format":"uuid","type":"string"}}],"responses":{"204":{"description":"Removed"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Missing, malformed or expired access token"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"The token does not carry the `admin` role"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Not found (or not published)"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationErrors"}}},"description":"The values were not acceptable"}},"security":[{"bearerAuth":[]}],"summary":"Stop requiring a stage","tags":["Admin: requirements"]}}},"security":[],"servers":[{"url":"/","variables":{}}],"tags":[{"description":"Published courses, stages, lessons, cheatsheets and learning paths. No sign-in.","name":"Catalog"},{"description":"Issuing a certificate for a badge, and verifying one by its public id.","name":"Certificates"},{"description":"The signed-in learner's progress and badges.","name":"Learner"},{"description":"Counts, time series and funnels for the dashboard; the named learner list is separate.","name":"Admin: analytics"},{"description":"One learner's progress, and resetting it: course progress, badges, certificates.","name":"Admin: learners"},{"description":"Authoring courses, stages and modules (drafts included).","name":"Admin: courses"},{"description":"Authoring lessons.","name":"Admin: lessons"},{"description":"Moving a whole course in or out as one JSON file. Production has no seeds: an admin imports courses.","name":"Admin: import and export"},{"description":"Authoring a course's cheatsheet.","name":"Admin: cheatsheets"},{"description":"Which stages should be done before another.","name":"Admin: requirements"},{"description":"Learning paths across courses.","name":"Admin: paths"},{"description":"Image uploads for lessons.","name":"Admin: uploads"},{"description":"This document.","name":"Documentation"}]}