Hierarchy
Tenant (Institution)
└── Course (title, slug, status, theme, publishAt, aiTutorEnabled)
└── CourseModule (title, sortOrder)
└── CourseLesson (type, content JSON, sortOrder, isPublished, publishAt)
├── CourseLessonMedia (N:N with MediaAsset)
└── LessonCompletion (1:N per studentId)
CRUD endpoints
POST /api/institution/courses
GET /api/institution/courses
GET /api/institution/courses/[id]
PATCH /api/institution/courses/[id]
DELETE /api/institution/courses/[id]
POST /api/institution/courses/[id]/clone
POST /api/institution/courses/import (IMS CC)
POST /api/institution/courses/[id]/modules
PATCH /api/institution/courses/[id]/modules/[mid]
DELETE /api/institution/courses/[id]/modules/[mid]
PATCH /api/institution/courses/[id]/modules/reorder
POST /api/institution/courses/[id]/modules/[mid]/lessons
PATCH /api/institution/courses/[id]/modules/[mid]/lessons/[lid]
DELETE /api/institution/courses/[id]/modules/[mid]/lessons/[lid]
Scopes: courses:read (GET) | courses:write (POST/PATCH/DELETE).
Course status
Course.status: draft | published | archived. Course.publishAt schedules automatic publishing via cron.
Scheduled publishing
Combine Course.publishAt with Lesson.publishAt for granular control. checkLessonAvailability() validates in ALL lesson endpoints.
Prerequisites
Lesson.prerequisiteLessonId points to lesson that MUST be complete before access.
Clone
POST /api/institution/courses/[id]/clone creates new Course with deep copy of modules + lessons. Does NOT copy: Enrollments, LessonCompletions, ClassGroups, QuizAttempts, RAG ingestion. Respects plan limit.
IMS CC import
Supports IMS CC v1.0, v1.1, v1.2, v1.3. Resources: web links, file resources, basic LTI links, QTI 1.2 quizzes, web content. NOT supported: H5P content packages, SCORM 2004 PIF, IMS CP.
Limitations
- Maximum 100 modules per course (soft limit, performance)
- Maximum 200 lessons per module (soft limit, UX)
- Content JSON per lesson up to 5MB
- Slug must be unique per tenant + language