Course Module API & Integration Guide
Complete admin, customer/mobile, and public API contract for course discovery, learning, quiz, and certificate flows.
Course - API & Integration Guide
Audience: Superadmin panel developers, mobile/web frontend developers, and backend integrators. Scope: Admin APIs, customer/mobile APIs, public verification API, DTO contracts, enums, flows, errors, and integration recipes.
1. How to Read / Quick Metadata
- Module:
Course - Owners: API Platform + Content/LMS team
- Environments:
dev,staging,prod - Auth models:
- Admin routes:
Admin JWT + RoleGuard + @Permissions - Customer routes:
User JWT - Public verify route: no auth
- Admin routes:
- Base URLs:
- Admin:
/api/admin/courses/*,/api/admin/certificates/* - Customer:
/api/courses/* - Mobile-composed customer:
/api/mobile/courses/* - Public:
/api/certificates/*
- Admin:
- Swagger groups (current):
- Admin:
Course Management (Admin),Course Topics (Admin),Course Lessons (Admin),Course Quiz (Admin),Course Resources (Admin),Course Reviews (Admin),Course Certificates (Admin) - Mobile/customer:
Course Discovery (Mobile),Course Purchases (Mobile),Course Learning (Mobile),Course Quiz (Mobile),Course Certificates (Mobile)
- Admin:
2. High-Level Overview
Course module provides LMS runtime on top of digital commerce entitlement (product_access).
Responsibilities:
- expose publish-ready course discovery and detail views,
- serve purchased curriculum/lesson runtime and progress updates,
- enforce quiz rules (eligibility, attempts, scoring, result retrieval),
- generate and expose course completion certificates,
- provide complete admin surface for course, curriculum, review, quiz, and certificate template management.
System fit:
- Catalog/Order integration: a course is linked to a
product(productType="course") and access is controlled by order/payment-createdproduct_access. - Tag/featured integration: course discovery filters tags through shared
product_tagand featured lists through sharedfeatured_product. - Async integration: certificate generation is queued through BullMQ (
QueueName.COURSES,CourseJob.GENERATE_CERTIFICATE). - Cache integration: list/detail endpoints are Redis-cached with deterministic keys built via
CacheKeyUtil.
3. Core Concepts & Terminology
- Course: sellable learning product with publishing lifecycle and optional quiz/certificate switches.
- Topic: ordered grouping of lessons in a course.
- Lesson: ordered learning unit (video/live/text/file/link).
- Resource: downloadable/link asset scoped to course, optionally to topic or lesson.
- Progress: user completion state by lesson.
- Quiz: per-course assessment definition (attempt cap, pass mark, time limit).
- Quiz Attempt: single user attempt containing answers + score + pass/fail.
- Certificate Template: rendering template metadata for certificate generation.
- User Certificate: issued certificate record for
(userId, courseId). - Access Validity: admin-configured
course.accessValidityDayscontrols purchase access duration.null= lifetime access. On repurchase, active access is extended (accessEndAt = existing.accessEndAt + validityDays).
Key lifecycle flags:
course.status:draft->published->hidden->archivedcourse.reviewMode:real|mocked|disabledcourse.quizEnabled: toggles quiz gatecourse.certificateEnabled: toggles certificate eligibilitycourse.accessValidityDays:null= lifetime, positive integer = days of access from purchase date
Schema guardrails:
certificate_template.nameis globally unique (certificate_template_name_unique)course.access_validity_daysis constrained tonullor a positive integer (course_access_validity_days_positive_check)- template creates are idempotent on full payload match; same name with different payload is rejected
Eligibility chain (certificate):
- certificate enabled on course
- user entitlement exists and is not expired
- all enabled lessons completed
- if quiz enabled, at least one passed completed attempt exists
4. Architecture & Data Flow
4.1 Module Interaction Diagram
4.2 Certificate Generation Runtime Flow
5. API Surface Summary
| Area | Method | Path | Actor | Description |
|---|---|---|---|---|
| Admin | GET | /api/admin/courses | Admin | List courses |
| Admin | GET | /api/admin/courses/:id | Admin | Course detail |
| Admin | POST | /api/admin/courses | Admin | Create course |
| Admin | PATCH | /api/admin/courses/:id | Admin | Update course |
| Admin | DELETE | /api/admin/courses/:id | Admin | Delete course |
| Admin | POST | /api/admin/courses/:courseId/link-product | Admin | Link product |
| Admin | DELETE | /api/admin/courses/:courseId/link-product | Admin | Unlink product |
| Admin | POST | /api/admin/courses/:courseId/auto-create-product | Admin | Auto-create + link product |
| Admin | PATCH | /api/admin/courses/:courseId/sellable | Admin | Toggle sellable |
| Admin | GET | /api/admin/courses/:courseId/topics | Admin | List topics |
| Admin | POST | /api/admin/courses/:courseId/topics | Admin | Create topic |
| Admin | PATCH | /api/admin/courses/:courseId/topics/:topicId | Admin | Update topic |
| Admin | DELETE | /api/admin/courses/:courseId/topics/:topicId | Admin | Delete topic |
| Admin | POST | /api/admin/courses/:courseId/topics/reorder | Admin | Reorder topics |
| Admin | GET | /api/admin/courses/:courseId/topics/:topicId/lessons | Admin | List lessons |
| Admin | POST | /api/admin/courses/:courseId/topics/:topicId/lessons | Admin | Create lesson |
| Admin | PATCH | /api/admin/courses/:courseId/topics/:topicId/lessons/:lessonId | Admin | Update lesson |
| Admin | DELETE | /api/admin/courses/:courseId/topics/:topicId/lessons/:lessonId | Admin | Delete lesson |
| Admin | POST | /api/admin/courses/:courseId/topics/:topicId/lessons/reorder | Admin | Reorder lessons |
| Admin | GET | /api/admin/courses/:courseId/resources | Admin | List resources |
| Admin | POST | /api/admin/courses/:courseId/resources | Admin | Create resource |
| Admin | DELETE | /api/admin/courses/:courseId/resources/:resourceId | Admin | Delete resource |
| Admin | GET | /api/admin/courses/:courseId/reviews | Admin | List reviews |
| Admin | POST | /api/admin/courses/:courseId/reviews | Admin | Create fake review |
| Admin | PATCH | /api/admin/courses/:courseId/reviews/:reviewId | Admin | Update review |
| Admin | POST | /api/admin/courses/:courseId/reviews/:reviewId/upload-image | Admin | Attach reviewer image |
| Admin | DELETE | /api/admin/courses/:courseId/reviews/:reviewId | Admin | Delete review |
| Admin | GET | /api/admin/courses/:courseId/quiz | Admin | Get quiz config |
| Admin | POST | /api/admin/courses/:courseId/quiz | Admin | Create quiz |
| Admin | PATCH | /api/admin/courses/:courseId/quiz | Admin | Update quiz |
| Admin | DELETE | /api/admin/courses/:courseId/quiz | Admin | Delete quiz |
| Admin | GET | /api/admin/courses/:courseId/quiz/questions | Admin | List quiz questions |
| Admin | POST | /api/admin/courses/:courseId/quiz/questions | Admin | Create question |
| Admin | PATCH | /api/admin/courses/:courseId/quiz/questions/:questionId | Admin | Update question |
| Admin | DELETE | /api/admin/courses/:courseId/quiz/questions/:questionId | Admin | Delete question |
| Admin | POST | /api/admin/courses/:courseId/quiz/questions/reorder | Admin | Reorder questions |
| Admin | POST | /api/admin/certificates/templates | Admin | Create template |
| Admin | GET | /api/admin/certificates/templates | Admin | List templates |
| Admin | GET | /api/admin/certificates/templates/:templateId | Admin | Template detail |
| Admin | PATCH | /api/admin/certificates/templates/:templateId | Admin | Update template |
| Admin | DELETE | /api/admin/certificates/templates/:templateId | Admin | Delete template |
| Admin | GET | /api/admin/certificates | Admin | List issued certificates |
| Customer | GET | /api/courses | Authenticated/anonymous | List published sellable courses |
| Customer | GET | /api/courses/featured | Authenticated/anonymous | List featured published sellable courses |
| Customer | GET | /api/courses/:slug | Authenticated/anonymous | Course detail by current/historical slug |
| Customer | GET | /api/courses/:slug/reviews | Authenticated/anonymous | List visible reviews by slug |
| Customer | GET | /api/courses/:slug/reviews/summary | Authenticated/anonymous | Review summary (count/average/distribution) |
| Customer | GET | /api/courses/purchases | Auth user | List purchased courses |
| Customer | GET | /api/courses/purchases/:purchaseId | Auth user | Purchase detail |
| Customer | GET | /api/courses/:courseId/curriculum | Auth user | Curriculum + completion summary |
| Customer | GET | /api/courses/:courseId/lessons/:lessonId | Auth user | Lesson content |
| Customer | POST | /api/courses/:courseId/lessons/:lessonId/complete | Auth user | Mark lesson complete |
| Customer | GET | /api/courses/:courseId/progress | Auth user | Detailed progress map |
| Customer | GET | /api/courses/:courseId/quiz | Auth user | Quiz eligibility/detail |
| Customer | POST | /api/courses/:courseId/quiz/start | Auth user | Start/reuse in-progress attempt |
| Customer | GET | /api/courses/:courseId/quiz/attempts | Auth user | List attempts |
| Customer | GET | /api/courses/:courseId/quiz/attempt/:attemptId | Auth user | Attempt detail |
| Customer | POST | /api/courses/:courseId/quiz/attempt/:attemptId/submit | Auth user | Submit answers and score |
| Customer | GET | /api/courses/certificates | Auth user | List certificates |
| Customer | GET | /api/courses/:courseId/certificate/eligibility | Auth user | Certificate eligibility |
| Customer | POST | /api/courses/:courseId/certificate/generate | Auth user | Queue/generate certificate |
| Customer | GET | /api/courses/:courseId/certificate | Auth user | Certificate detail |
| Public | GET | /api/certificates/verify/:certificateNumber | Public | Verify certificate |
All customer routes are also mounted under
/api/mobile/courses/*viamobile.module.tscomposition.
5.1 Swagger Group to Path Mapping
| Swagger group | Primary paths |
|---|---|
Course Management (Admin) | /api/admin/courses, /api/admin/courses/:id, /api/admin/courses/:courseId/link-product, /api/admin/courses/:courseId/auto-create-product, /api/admin/courses/:courseId/sellable |
Course Topics (Admin) | /api/admin/courses/:courseId/topics* |
Course Lessons (Admin) | /api/admin/courses/:courseId/topics/:topicId/lessons* |
Course Quiz (Admin) | /api/admin/courses/:courseId/quiz* |
Course Resources (Admin) | /api/admin/courses/:courseId/resources* |
Course Reviews (Admin) | /api/admin/courses/:courseId/reviews* |
Course Certificates (Admin) | /api/admin/certificates* |
Course Discovery (Mobile) | /api/courses, /api/courses/featured, /api/courses/:slug (+ /api/mobile/courses mirror) |
Course Purchases (Mobile) | /api/courses/purchases* (+ /api/mobile/courses/purchases* mirror) |
Course Learning (Mobile) | /api/courses/:courseId/curriculum, /api/courses/:courseId/lessons/:lessonId, /api/courses/:courseId/progress (+ mobile mirror) |
Course Quiz (Mobile) | /api/courses/:courseId/quiz* (+ mobile mirror) |
Course Certificates (Mobile) | /api/courses/certificates, /api/courses/:courseId/certificate* (+ mobile mirror) |
Course Reviews (Mobile) | /api/courses/:slug/reviews* (+ /api/mobile/courses/:slug/reviews* mirror) |
6. Data Models & Enums
6.1 Key Response DTOs
CourseListItemDto
Used in: GET /api/courses, GET /api/admin/courses
{
"id": 101,
"title": "Complete Stock Market Trading Course",
"slug": "complete-stock-market-trading-course",
"shortSummary": "Learn market structure and risk management.",
"status": "published",
"isSellable": true,
"categoryId": 3
}CurriculumResponseDto
Used in: GET /api/courses/:courseId/curriculum
{
"courseId": 101,
"courseTitle": "Complete Stock Market Trading Course",
"topics": [
{
"id": 5,
"title": "Introduction",
"description": "Core market fundamentals.",
"orderIndex": 1,
"lessons": [
{
"id": 11,
"title": "What is NEPSE?",
"lessonType": "video",
"duration": 15,
"orderIndex": 1,
"isPreview": false,
"isCompleted": true
}
]
}
],
"totalLessons": 3,
"completedLessons": 1,
"progressPercent": 33.33
}QuizDetailResponseDto
Used in: GET /api/courses/:courseId/quiz
{
"id": 12,
"courseId": 101,
"title": "Module 1 Final Assessment",
"description": "Complete this quiz to unlock certificate eligibility.",
"quizAttempts": 3,
"passMark": 60,
"timeLimit": 30,
"shuffleQuestions": false,
"showResults": true,
"isEligible": true,
"notEligibleReason": null,
"totalAttempts": 1,
"remainingAttempts": 2,
"hasInProgressAttempt": false,
"lastAttempt": {
"attemptId": 88,
"score": 7,
"passed": true,
"completedAt": "2026-03-19T10:20:00.000Z"
}
}SubmitQuizDto (request)
Used in: POST /api/courses/:courseId/quiz/attempt/:attemptId/submit
{
"answers": {
"44": "Market Order",
"45": "true",
"46": ["bull", "bullish"]
}
}CertificateGenerationResponseDto
Used in: POST /api/courses/:courseId/certificate/generate
{
"status": "queued",
"message": "Certificate generation has been queued successfully."
}CertificateVerifyResponseDto
Used in: GET /api/certificates/verify/:certificateNumber
{
"isValid": true,
"studentName": "Jane Doe",
"courseName": "Complete Stock Market Trading",
"issuedAt": "2026-03-19T10:00:00.000Z",
"certificateNumber": "CERT-20260319-384726"
}6.2 Important Request DTOs (Admin)
CreateCourseDto/UpdateCourseDtoCreateTopicDto/UpdateTopicDto/ReorderTopicsDtoCreateLessonDto/UpdateLessonDto/ReorderLessonsDtoCreateResourceDtoCreateReviewDto/UpdateReviewDto/FetchReviewDtoCreateQuizDto/UpdateQuizDtoCreateQuestionDto/UpdateQuestionDto/ReorderQuestionsDtoCreateCertificateTemplateDto/UpdateCertificateTemplateDtoFetchCourseDto,FetchCertificateTemplateDto,FetchCertificateDto
6.3 Enums
course_status
| Value | Meaning |
|---|---|
draft | WIP, non-public |
published | Publicly discoverable |
hidden | Not publicly discoverable |
archived | Retained, inactive |
course_lesson_type
| Value | Meaning |
|---|---|
video | video asset reference required |
live | live session reference required |
text | textual content lesson |
file | downloadable file reference required |
link | external URL lesson |
course_review_mode
| Value | Meaning |
|---|---|
real | customer-generated reviews |
mocked | admin-seeded mocked reviews allowed |
disabled | review display disabled |
resource_type
| Value | Meaning |
|---|---|
file | downloadable file |
link | external URL |
quiz_question_type
| Value | Meaning |
|---|---|
multiple_choice | single string answer from options |
true_false | boolean-style string answer ("true"/"false") |
short_answer | string or string[] normalized answer |
Eligibility reason enums
certificate.notEligibleReason values:
certificate_not_enablednot_enrolledaccess_expiredcourse_incompletequiz_not_passednull
quiz.notEligibleReason values:
course_not_completemax_attempts_reachednull
7. Endpoint Reference + Payload Cheatsheet (All APIs)
7.1 Common Auth and Response Wrapper
- Response wrapper follows
ResponseDto<T>:- success payload in
data - paginated list metadata only when pagination enabled
- success payload in
- Pagination normalization uses
PaginationUtil.normalize({ pagination, page, size }) - Rate limit key pattern:
- Admin:
rl:courses:admin:action:<action>-key:<clientKey> - Customer:
rl:courses:customer:action:<action>-key:<clientKey> - Public verify:
rl:certificates:public:action:cert-verify-key:<clientKey>
- Admin:
7.2 Payload Cheatsheet Table (Every Endpoint)
| Method | Path | Auth / Permission | Request DTO / Params | Success DTO | Notes |
|---|---|---|---|---|---|
| GET | /api/admin/courses | Admin + Courses_READ | FetchCourseDto | CourseListItemDto[] paginated | filters: status/category/tag/sellable/search/sort |
| GET | /api/admin/courses/:id | Admin + Courses_READ | id:int | CourseResponseDto | detail by id |
| POST | /api/admin/courses | Admin + Courses_CREATE | CreateCourseDto | CourseResponseDto | title/slug/product/template validation |
| PATCH | /api/admin/courses/:id | Admin + Courses_UPDATE | id:int, UpdateCourseDto | CourseResponseDto | nullable fields supported |
| DELETE | /api/admin/courses/:id | Admin + Courses_DELETE | id:int | CourseDeleteResponseDto | returns {id, deleted:true} |
| POST | /api/admin/courses/:courseId/link-product | Admin + Courses_UPDATE | courseId:int, LinkProductDto | CourseResponseDto | product must be productType=course |
| DELETE | /api/admin/courses/:courseId/link-product | Admin + Courses_UPDATE | courseId:int | CourseResponseDto | unlink product |
| POST | /api/admin/courses/:courseId/auto-create-product | Admin + Courses_CREATE | courseId:int | CourseResponseDto | creates and links catalog product |
| PATCH | /api/admin/courses/:courseId/sellable | Admin + Courses_UPDATE | courseId:int, UpdateCourseDto (isSellable required) | CourseResponseDto | fails if isSellable missing |
| GET | /api/admin/courses/:courseId/topics | Admin + Courses_READ | courseId:int, QueryDto | TopicResponseDto[] paginated | topic list |
| POST | /api/admin/courses/:courseId/topics | Admin + Courses_CREATE | courseId:int, CreateTopicDto | TopicResponseDto | append/insert by orderIndex |
| PATCH | /api/admin/courses/:courseId/topics/:topicId | Admin + Courses_UPDATE | UpdateTopicDto | TopicResponseDto | validates course-topic ownership |
| DELETE | /api/admin/courses/:courseId/topics/:topicId | Admin + Courses_DELETE | path params | CourseDeleteResponseDto | topic delete |
| POST | /api/admin/courses/:courseId/topics/reorder | Admin + Courses_UPDATE | ReorderTopicsDto | ResponseDto<void> | input must contain complete, unique topic IDs |
| GET | /api/admin/courses/:courseId/topics/:topicId/lessons | Admin + Courses_READ | QueryDto | LessonResponseDto[] paginated | lesson list |
| POST | /api/admin/courses/:courseId/topics/:topicId/lessons | Admin + Courses_CREATE | CreateLessonDto | LessonResponseDto | lesson-type payload validation |
| PATCH | /api/admin/courses/:courseId/topics/:topicId/lessons/:lessonId | Admin + Courses_UPDATE | UpdateLessonDto | LessonResponseDto | nullable media/content fields |
| DELETE | /api/admin/courses/:courseId/topics/:topicId/lessons/:lessonId | Admin + Courses_DELETE | path params | CourseDeleteResponseDto | lesson delete |
| POST | /api/admin/courses/:courseId/topics/:topicId/lessons/reorder | Admin + Courses_UPDATE | ReorderLessonsDto | ResponseDto<void> | ordered complete list required |
| GET | /api/admin/courses/:courseId/resources | Admin + Courses_READ | QueryDto | ResourceResponseDto[] paginated | course resources |
| POST | /api/admin/courses/:courseId/resources | Admin + Courses_CREATE | CreateResourceDto | ResourceResponseDto | at most one of topicId/lessonId |
| DELETE | /api/admin/courses/:courseId/resources/:resourceId | Admin + Courses_DELETE | path params | CourseDeleteResponseDto | resource delete |
| GET | /api/admin/courses/:courseId/reviews | Admin + Courses_READ | FetchReviewDto | ReviewResponseDto[] paginated | filter hidden, sort by createdAt/rating |
| POST | /api/admin/courses/:courseId/reviews | Admin + Courses_CREATE | CreateReviewDto | ReviewResponseDto | fake reviewer fields (reviewerName, reviewerEmail) |
| PATCH | /api/admin/courses/:courseId/reviews/:reviewId | Admin + Courses_UPDATE | UpdateReviewDto | ReviewResponseDto | hide/unhide/edit rating/comment/reviewer/image fields |
| POST | /api/admin/courses/:courseId/reviews/:reviewId/upload-image | Admin + Courses_UPDATE | body with imageUrl + imageRef | ReviewResponseDto | validates image payload and updates review |
| DELETE | /api/admin/courses/:courseId/reviews/:reviewId | Admin + Courses_DELETE | path params | CourseDeleteResponseDto | review delete |
| GET | /api/admin/courses/:courseId/quiz | Admin + Courses_READ | courseId:int | QuizResponseDto | quiz config |
| POST | /api/admin/courses/:courseId/quiz | Admin + Courses_CREATE | CreateQuizDto | QuizResponseDto | one quiz per course |
| PATCH | /api/admin/courses/:courseId/quiz | Admin + Courses_UPDATE | UpdateQuizDto | QuizResponseDto | validates passMark/attempts/timeLimit |
| DELETE | /api/admin/courses/:courseId/quiz | Admin + Courses_DELETE | courseId:int | QuizDeleteResponseDto | quiz delete |
| GET | /api/admin/courses/:courseId/quiz/questions | Admin + Courses_READ | FetchQuizDto | QuestionResponseDto[] paginated | list questions |
| POST | /api/admin/courses/:courseId/quiz/questions | Admin + Courses_CREATE | CreateQuestionDto | QuestionResponseDto | validates by questionType |
| PATCH | /api/admin/courses/:courseId/quiz/questions/:questionId | Admin + Courses_UPDATE | UpdateQuestionDto | QuestionResponseDto | when options or questionType changes, include matching correctAnswer in same request |
| DELETE | /api/admin/courses/:courseId/quiz/questions/:questionId | Admin + Courses_DELETE | path params | QuestionDeleteResponseDto | question delete |
| POST | /api/admin/courses/:courseId/quiz/questions/reorder | Admin + Courses_UPDATE | ReorderQuestionsDto | ReorderQuestionsResponseDto | complete sequence required |
| POST | /api/admin/certificates/templates | Admin + Courses_CREATE | CreateCertificateTemplateDto | CertificateTemplateResponseDto | unique name constraints enforced |
| GET | /api/admin/certificates/templates | Admin + Courses_READ | FetchCertificateTemplateDto | CertificateTemplateResponseDto[] paginated | cached list |
| GET | /api/admin/certificates/templates/:templateId | Admin + Courses_READ | templateId:int | CertificateTemplateResponseDto | cached detail |
| PATCH | /api/admin/certificates/templates/:templateId | Admin + Courses_UPDATE | UpdateCertificateTemplateDto | CertificateTemplateResponseDto | backgroundImageUrl nullable |
| DELETE | /api/admin/certificates/templates/:templateId | Admin + Courses_DELETE | templateId:int | TemplateDeleteResponseDto | blocked if referenced |
| GET | /api/admin/certificates | Admin + Courses_READ | FetchCertificateDto | CertificateListItemDto[] paginated | issued certificates list |
| GET | /api/courses | Optional auth | CourseListQueryDto | CourseListItemDto[] paginated | published + sellable only |
| GET | /api/courses/featured | Optional auth | CourseListQueryDto | CourseListItemDto[] paginated | featured + published + sellable |
| GET | /api/courses/:slug | Optional auth | slug:string | CourseResponseDto | supports slug history fallback |
| GET | /api/courses/:slug/reviews | Optional auth | FetchCourseReviewDto | CourseReviewDto[] paginated | visible reviews only (isHidden=false) |
| GET | /api/courses/:slug/reviews/summary | Optional auth | slug:string | CourseReviewSummaryDto | returns totalCount, averageRating, ratingDistribution |
| GET | /api/courses/purchases | User JWT | PurchaseListQueryDto | PurchaseListItemDto[] paginated | purchased catalog |
| GET | /api/courses/purchases/:purchaseId | User JWT | purchaseId:int | PurchaseDetailDto | ownership enforced |
| GET | /api/courses/:courseId/curriculum | User JWT | path params | CurriculumResponseDto | entitlement required |
| GET | /api/courses/:courseId/lessons/:lessonId | User JWT | path params | LessonContentResponseDto | verifies lesson belongs to course |
| POST | /api/courses/:courseId/lessons/:lessonId/complete | User JWT | path params | LessonCompleteResponseDto | idempotent completion upsert |
| GET | /api/courses/:courseId/progress | User JWT | path params | CourseProgressResponseDto | per-topic/lesson progress map |
| GET | /api/courses/:courseId/quiz | User JWT | path params | QuizDetailResponseDto | includes eligibility + attempt stats |
| POST | /api/courses/:courseId/quiz/start | User JWT | path params | StartQuizResponseDto | returns in-progress attempt if present |
| GET | /api/courses/:courseId/quiz/attempts | User JWT | QueryDto | QuizAttemptListItemDto[] paginated | attempt history |
| GET | /api/courses/:courseId/quiz/attempt/:attemptId | User JWT | path params | AttemptResponseDto | detailed answers/questions |
| POST | /api/courses/:courseId/quiz/attempt/:attemptId/submit | User JWT | SubmitQuizDto | SubmitQuizResponseDto | prevents double submit / expired attempt |
| GET | /api/courses/certificates | User JWT | QueryDto | CertificateListItemDto[] paginated | user certificate list |
| GET | /api/courses/:courseId/certificate/eligibility | User JWT | path params | CertificateEligibilityResponseDto | explicit reason codes |
| POST | /api/courses/:courseId/certificate/generate | User JWT | path params | CertificateGenerationResponseDto | queue dedupe with jobId=cert:<userId>:<courseId> |
| GET | /api/courses/:courseId/certificate | User JWT | path params | CertificateResponseDto | returns cert or domain error |
| GET | /api/certificates/verify/:certificateNumber | Public | certificateNumber:string | CertificateVerifyResponseDto | no-auth verification |
7.2.1 Quiz Question Update Contract
For PATCH /api/admin/courses/:courseId/quiz/questions/:questionId:
- If you update
options, includecorrectAnswerin the same payload. - If you update
questionType, include compatiblecorrectAnswer(andoptionswhere required) in the same payload. multiple_choice:correctAnswermust be a string and exactly match one option.true_false: options must resolve to["true", "false"]andcorrectAnswermust be"true"or"false".short_answer:correctAnswercan bestringorstring[];optionsshould be omitted ornull.
Example:
{
"options": ["Market Order", "Limit Order", "Stop Loss"],
"correctAnswer": "Market Order",
"explanation": null
}7.3 Error Codes
| HTTP | errorCode | Condition |
|---|---|---|
| 400 | COURSE_TOPIC_CREATE_FAILED | Topic creation failed |
| 400 | COURSE_LESSON_CREATE_FAILED | Lesson creation failed |
| 400 | COURSE_RESOURCE_CREATE_FAILED | Resource creation failed |
| 400 | COURSE_REVIEW_CREATE_FAILED | Review creation failed |
| 400 | COURSE_REVIEW_INVALID_RATING | Review rating is outside 1..5 |
| 400 | COURSE_REVIEW_UPLOAD_FAILED | Review image upload payload/operation failed |
| 400 | COURSE_LINK_FAILED | Failed to create course or product already linked |
| 400 | COURSE_SELLABLE_REQUIRED | Product type is not 'course' |
| 400 | COURSE_LESSON_MISMATCH | Resource belongs to wrong course/topic |
| 400 | QUIZ_MAX_ATTEMPTS_REACHED | quizAttempts ≤ 0 or user max attempts reached |
| 400 | QUIZ_INVALID_ANSWER_FORMAT | Invalid passMark, timeLimit, or answer format |
| 400 | QUIZ_ANSWER_INVALID_TYPE | Invalid question answer type or options |
| 400 | QUIZ_EXISTS_FOR_COURSE | Quiz already exists for this course |
| 400 | QUIZ_UNABLE_TO_START | Unable to start quiz attempt |
| 400 | QUIZ_COURSE_INCOMPLETE | Course not completed before quiz |
| 400 | QUIZ_ALREADY_SUBMITTED | Quiz already submitted |
| 400 | QUIZ_TIME_LIMIT_EXCEEDED | Quiz time limit exceeded |
| 400 | QUIZ_ORDER_INDEX_REQUIRED | orderIndex missing in request |
| 400 | QUIZ_REORDER_* | Invalid question reorder payload |
| 400 | CERTIFICATE_CREATE_FAILED | Certificate template creation failed |
| 400 | CERTIFICATE_TEMPLATE_HAS_PUBLISHED_COURSES | Template has published courses |
| 400 | CERTIFICATE_QUEUE_FAILED | Certificate queue job failed |
| 403 | COURSE_ACCESS_DENIED | User does not own the resource |
| 403 | COURSE_ACCESS_EXPIRED | Course access has expired |
| 404 | COURSE_NOT_FOUND | Course does not exist |
| 404 | COURSE_TOPIC_NOT_FOUND | Topic does not exist |
| 404 | COURSE_LESSON_NOT_FOUND | Lesson does not exist |
| 404 | COURSE_RESOURCE_NOT_FOUND | Resource does not exist |
| 404 | COURSE_REVIEW_NOT_FOUND | Review does not exist |
| 404 | COURSE_PRODUCT_NOT_FOUND | Product does not exist |
| 404 | COURSE_CATEGORY_NOT_FOUND | Category does not exist |
| 404 | COURSE_CUSTOMER_NOT_FOUND | Customer does not exist |
| 404 | COURSE_PURCHASE_NOT_FOUND | Purchase does not exist |
| 404 | QUIZ_NOT_FOUND | Quiz does not exist |
| 404 | QUIZ_ATTEMPT_NOT_FOUND | Quiz attempt does not exist |
| 404 | QUIZ_QUESTION_NOT_FOUND | Question does not exist |
| 404 | CERTIFICATE_NOT_FOUND | Certificate does not exist |
| 404 | CERTIFICATE_TEMPLATE_NOT_FOUND | Certificate template does not exist |
| 409 | COURSE_LINK_FAILED | Product already linked to another course |
| 429 | RATE_LIMIT_EXCEEDED | Rate limit exceeded |
| 500 | CERTIFICATE_NUMBER_GENERATION_FAILED | Certificate number generation failed |
8. Common Flows / Recipes and Client Integration Guidelines
8.1 Admin Flow: Build Publishable Course
POST /api/admin/coursesPOST /api/admin/courses/:courseId/topicsPOST /api/admin/courses/:courseId/topics/:topicId/lessons- Optional: add resources/reviews/quiz/template
PATCH /api/admin/courses/:idwithstatus="published",isSellable=true- verify in customer listing via
/api/courses
8.2 Mobile Flow: Learn + Quiz + Certificate
- List purchases:
GET /api/mobile/courses/purchases - Open curriculum:
GET /api/mobile/courses/:courseId/curriculum - Mark lessons complete per lesson
- Open quiz detail and start/submit attempt
- Check eligibility
- Generate certificate (queue)
- Fetch certificate detail/list and show download URL when available
8.3 Public Verification Flow
- Input certificate number
GET /api/certificates/verify/:certificateNumber- If
isValid=false, show invalid state - If
isValid=true, render student/course/date summary
8.4 Mobile App
- Keep learner state machine explicit:
not_purchased->learning->quiz_pending->certificate_pending->certified
- Polling strategy for certificate generation:
- after
status=queued, pollGET /certificatewith exponential backoff
- after
- Respect rate limits; centralize
429handling and retry with jitter. - Cache recommendations:
/courseslist and/courses/:slugdetail can be cached client-side for short TTL- avoid long-lived cache for progress/quiz attempts/certificate status
- Suggested screen mapping:
- Discover screen:
/courses - Course detail:
/courses/:slug - My learning: purchases + curriculum + progress
- Quiz screen: quiz detail/start/submit/attempts
- Certificate screen: eligibility/generate/detail/list
- Discover screen:
8.5 Superadmin Panel
- Course editor should split tabs:
Course Info,Curriculum,Resources,Reviews,Quiz,Certificate Template. - Lock destructive actions when status is
archivedunless explicit restore workflow exists. - For reorder APIs, always submit complete ordered ID arrays (not partial reorder deltas).
- Use list filters directly via query DTO fields (status/category/review flags/sort/order).
9. Validation Rules & Edge Cases
Course runtime depends on both content structure and commerce entitlement. The most common integration bug is treating a published course as automatically accessible; it is not. Learning and certificate routes require valid product_access, and expired access returns forbidden even if progress exists. Slug-based discovery accepts historical slugs, but only for currently published and isSellable=true courses; hidden/archived records are not exposed. Quiz behavior has strict guardrails: users cannot start until course completion, cannot exceed configured attempt count, cannot submit an already completed attempt, and cannot submit after the time limit. Certificate generation is intentionally asynchronous and idempotent: repeated generate calls for the same (userId, courseId) use deterministic BullMQ jobId dedupe and DB conflict-safe insert semantics. Public verification intentionally never throws not-found; it returns isValid=false to keep verifier UX simple. For admin mutations, ordering arrays must be complete and unique, and lesson/resource payloads must match their type-specific requirements (videoAssetRef, fileRef, linkUrl, etc.).
10. Testing, Sandbox & Example Scenarios
10.1 Suggested Test Scenarios
- Admin creates full course, publishes it, and course appears in customer list.
- Course slug changed in admin; old slug still resolves to canonical detail.
- User without entitlement hits curriculum/quiz/certificate endpoints and receives
403. - User completes all lessons then can start quiz; before completion receives
400. - User reaches max quiz attempts and receives
400 Maximum quiz attempts reached. - User submits after time limit and receives
400 Time limit exceeded. - Certificate generate queues exactly one job for same
(userId, courseId)via deterministic job id. - Public verification returns
isValid=falsefor unknown certificate number. 429behavior for admin/customer/public route families with sliding window Redis keys.
10.2 Example Seed Data
{
"course": {
"id": 101,
"title": "Complete Stock Market Trading Course",
"status": "published",
"isSellable": true,
"quizEnabled": true,
"certificateEnabled": true
},
"quiz": {
"quizAttempts": 3,
"passMark": 60,
"timeLimit": 30,
"questions": 20
},
"user": {
"id": "4f0f5f62-4f79-4d5b-a5d9-7f71a5f89d27",
"hasProductAccess": true,
"courseCompleted": true,
"passedQuiz": true
},
"certificate": {
"certificateNumber": "CERT-20260319-384726"
}
}Time fields in this module are stored as timezone-aware values and should be handled as ISO-8601 instants by API consumers.
See Also
- Feature Guide: See Course - Feature List Section 6 (State Models) for course and lesson lifecycle diagrams.
- Backend Reference: See Course - Backend Documentation Section 9 for system architecture diagram.