Shop It Docs
Developer Resourcescourse

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
  • Base URLs:
    • Admin: /api/admin/courses/*, /api/admin/certificates/*
    • Customer: /api/courses/*
    • Mobile-composed customer: /api/mobile/courses/*
    • Public: /api/certificates/*
  • 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)

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-created product_access.
  • Tag/featured integration: course discovery filters tags through shared product_tag and featured lists through shared featured_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.accessValidityDays controls purchase access duration. null = lifetime access. On repurchase, active access is extended (accessEndAt = existing.accessEndAt + validityDays).

Key lifecycle flags:

  • course.status: draft -> published -> hidden -> archived
  • course.reviewMode: real | mocked | disabled
  • course.quizEnabled: toggles quiz gate
  • course.certificateEnabled: toggles certificate eligibility
  • course.accessValidityDays: null = lifetime, positive integer = days of access from purchase date

Schema guardrails:

  • certificate_template.name is globally unique (certificate_template_name_unique)
  • course.access_validity_days is constrained to null or 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):

  1. certificate enabled on course
  2. user entitlement exists and is not expired
  3. all enabled lessons completed
  4. 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

AreaMethodPathActorDescription
AdminGET/api/admin/coursesAdminList courses
AdminGET/api/admin/courses/:idAdminCourse detail
AdminPOST/api/admin/coursesAdminCreate course
AdminPATCH/api/admin/courses/:idAdminUpdate course
AdminDELETE/api/admin/courses/:idAdminDelete course
AdminPOST/api/admin/courses/:courseId/link-productAdminLink product
AdminDELETE/api/admin/courses/:courseId/link-productAdminUnlink product
AdminPOST/api/admin/courses/:courseId/auto-create-productAdminAuto-create + link product
AdminPATCH/api/admin/courses/:courseId/sellableAdminToggle sellable
AdminGET/api/admin/courses/:courseId/topicsAdminList topics
AdminPOST/api/admin/courses/:courseId/topicsAdminCreate topic
AdminPATCH/api/admin/courses/:courseId/topics/:topicIdAdminUpdate topic
AdminDELETE/api/admin/courses/:courseId/topics/:topicIdAdminDelete topic
AdminPOST/api/admin/courses/:courseId/topics/reorderAdminReorder topics
AdminGET/api/admin/courses/:courseId/topics/:topicId/lessonsAdminList lessons
AdminPOST/api/admin/courses/:courseId/topics/:topicId/lessonsAdminCreate lesson
AdminPATCH/api/admin/courses/:courseId/topics/:topicId/lessons/:lessonIdAdminUpdate lesson
AdminDELETE/api/admin/courses/:courseId/topics/:topicId/lessons/:lessonIdAdminDelete lesson
AdminPOST/api/admin/courses/:courseId/topics/:topicId/lessons/reorderAdminReorder lessons
AdminGET/api/admin/courses/:courseId/resourcesAdminList resources
AdminPOST/api/admin/courses/:courseId/resourcesAdminCreate resource
AdminDELETE/api/admin/courses/:courseId/resources/:resourceIdAdminDelete resource
AdminGET/api/admin/courses/:courseId/reviewsAdminList reviews
AdminPOST/api/admin/courses/:courseId/reviewsAdminCreate fake review
AdminPATCH/api/admin/courses/:courseId/reviews/:reviewIdAdminUpdate review
AdminPOST/api/admin/courses/:courseId/reviews/:reviewId/upload-imageAdminAttach reviewer image
AdminDELETE/api/admin/courses/:courseId/reviews/:reviewIdAdminDelete review
AdminGET/api/admin/courses/:courseId/quizAdminGet quiz config
AdminPOST/api/admin/courses/:courseId/quizAdminCreate quiz
AdminPATCH/api/admin/courses/:courseId/quizAdminUpdate quiz
AdminDELETE/api/admin/courses/:courseId/quizAdminDelete quiz
AdminGET/api/admin/courses/:courseId/quiz/questionsAdminList quiz questions
AdminPOST/api/admin/courses/:courseId/quiz/questionsAdminCreate question
AdminPATCH/api/admin/courses/:courseId/quiz/questions/:questionIdAdminUpdate question
AdminDELETE/api/admin/courses/:courseId/quiz/questions/:questionIdAdminDelete question
AdminPOST/api/admin/courses/:courseId/quiz/questions/reorderAdminReorder questions
AdminPOST/api/admin/certificates/templatesAdminCreate template
AdminGET/api/admin/certificates/templatesAdminList templates
AdminGET/api/admin/certificates/templates/:templateIdAdminTemplate detail
AdminPATCH/api/admin/certificates/templates/:templateIdAdminUpdate template
AdminDELETE/api/admin/certificates/templates/:templateIdAdminDelete template
AdminGET/api/admin/certificatesAdminList issued certificates
CustomerGET/api/coursesAuthenticated/anonymousList published sellable courses
CustomerGET/api/courses/featuredAuthenticated/anonymousList featured published sellable courses
CustomerGET/api/courses/:slugAuthenticated/anonymousCourse detail by current/historical slug
CustomerGET/api/courses/:slug/reviewsAuthenticated/anonymousList visible reviews by slug
CustomerGET/api/courses/:slug/reviews/summaryAuthenticated/anonymousReview summary (count/average/distribution)
CustomerGET/api/courses/purchasesAuth userList purchased courses
CustomerGET/api/courses/purchases/:purchaseIdAuth userPurchase detail
CustomerGET/api/courses/:courseId/curriculumAuth userCurriculum + completion summary
CustomerGET/api/courses/:courseId/lessons/:lessonIdAuth userLesson content
CustomerPOST/api/courses/:courseId/lessons/:lessonId/completeAuth userMark lesson complete
CustomerGET/api/courses/:courseId/progressAuth userDetailed progress map
CustomerGET/api/courses/:courseId/quizAuth userQuiz eligibility/detail
CustomerPOST/api/courses/:courseId/quiz/startAuth userStart/reuse in-progress attempt
CustomerGET/api/courses/:courseId/quiz/attemptsAuth userList attempts
CustomerGET/api/courses/:courseId/quiz/attempt/:attemptIdAuth userAttempt detail
CustomerPOST/api/courses/:courseId/quiz/attempt/:attemptId/submitAuth userSubmit answers and score
CustomerGET/api/courses/certificatesAuth userList certificates
CustomerGET/api/courses/:courseId/certificate/eligibilityAuth userCertificate eligibility
CustomerPOST/api/courses/:courseId/certificate/generateAuth userQueue/generate certificate
CustomerGET/api/courses/:courseId/certificateAuth userCertificate detail
PublicGET/api/certificates/verify/:certificateNumberPublicVerify certificate

All customer routes are also mounted under /api/mobile/courses/* via mobile.module.ts composition.

5.1 Swagger Group to Path Mapping

Swagger groupPrimary 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 / UpdateCourseDto
  • CreateTopicDto / UpdateTopicDto / ReorderTopicsDto
  • CreateLessonDto / UpdateLessonDto / ReorderLessonsDto
  • CreateResourceDto
  • CreateReviewDto / UpdateReviewDto / FetchReviewDto
  • CreateQuizDto / UpdateQuizDto
  • CreateQuestionDto / UpdateQuestionDto / ReorderQuestionsDto
  • CreateCertificateTemplateDto / UpdateCertificateTemplateDto
  • FetchCourseDto, FetchCertificateTemplateDto, FetchCertificateDto

6.3 Enums

course_status

ValueMeaning
draftWIP, non-public
publishedPublicly discoverable
hiddenNot publicly discoverable
archivedRetained, inactive

course_lesson_type

ValueMeaning
videovideo asset reference required
livelive session reference required
texttextual content lesson
filedownloadable file reference required
linkexternal URL lesson

course_review_mode

ValueMeaning
realcustomer-generated reviews
mockedadmin-seeded mocked reviews allowed
disabledreview display disabled

resource_type

ValueMeaning
filedownloadable file
linkexternal URL

quiz_question_type

ValueMeaning
multiple_choicesingle string answer from options
true_falseboolean-style string answer ("true"/"false")
short_answerstring or string[] normalized answer

Eligibility reason enums

certificate.notEligibleReason values:

  • certificate_not_enabled
  • not_enrolled
  • access_expired
  • course_incomplete
  • quiz_not_passed
  • null

quiz.notEligibleReason values:

  • course_not_complete
  • max_attempts_reached
  • null

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
  • 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>

7.2 Payload Cheatsheet Table (Every Endpoint)

MethodPathAuth / PermissionRequest DTO / ParamsSuccess DTONotes
GET/api/admin/coursesAdmin + Courses_READFetchCourseDtoCourseListItemDto[] paginatedfilters: status/category/tag/sellable/search/sort
GET/api/admin/courses/:idAdmin + Courses_READid:intCourseResponseDtodetail by id
POST/api/admin/coursesAdmin + Courses_CREATECreateCourseDtoCourseResponseDtotitle/slug/product/template validation
PATCH/api/admin/courses/:idAdmin + Courses_UPDATEid:int, UpdateCourseDtoCourseResponseDtonullable fields supported
DELETE/api/admin/courses/:idAdmin + Courses_DELETEid:intCourseDeleteResponseDtoreturns {id, deleted:true}
POST/api/admin/courses/:courseId/link-productAdmin + Courses_UPDATEcourseId:int, LinkProductDtoCourseResponseDtoproduct must be productType=course
DELETE/api/admin/courses/:courseId/link-productAdmin + Courses_UPDATEcourseId:intCourseResponseDtounlink product
POST/api/admin/courses/:courseId/auto-create-productAdmin + Courses_CREATEcourseId:intCourseResponseDtocreates and links catalog product
PATCH/api/admin/courses/:courseId/sellableAdmin + Courses_UPDATEcourseId:int, UpdateCourseDto (isSellable required)CourseResponseDtofails if isSellable missing
GET/api/admin/courses/:courseId/topicsAdmin + Courses_READcourseId:int, QueryDtoTopicResponseDto[] paginatedtopic list
POST/api/admin/courses/:courseId/topicsAdmin + Courses_CREATEcourseId:int, CreateTopicDtoTopicResponseDtoappend/insert by orderIndex
PATCH/api/admin/courses/:courseId/topics/:topicIdAdmin + Courses_UPDATEUpdateTopicDtoTopicResponseDtovalidates course-topic ownership
DELETE/api/admin/courses/:courseId/topics/:topicIdAdmin + Courses_DELETEpath paramsCourseDeleteResponseDtotopic delete
POST/api/admin/courses/:courseId/topics/reorderAdmin + Courses_UPDATEReorderTopicsDtoResponseDto<void>input must contain complete, unique topic IDs
GET/api/admin/courses/:courseId/topics/:topicId/lessonsAdmin + Courses_READQueryDtoLessonResponseDto[] paginatedlesson list
POST/api/admin/courses/:courseId/topics/:topicId/lessonsAdmin + Courses_CREATECreateLessonDtoLessonResponseDtolesson-type payload validation
PATCH/api/admin/courses/:courseId/topics/:topicId/lessons/:lessonIdAdmin + Courses_UPDATEUpdateLessonDtoLessonResponseDtonullable media/content fields
DELETE/api/admin/courses/:courseId/topics/:topicId/lessons/:lessonIdAdmin + Courses_DELETEpath paramsCourseDeleteResponseDtolesson delete
POST/api/admin/courses/:courseId/topics/:topicId/lessons/reorderAdmin + Courses_UPDATEReorderLessonsDtoResponseDto<void>ordered complete list required
GET/api/admin/courses/:courseId/resourcesAdmin + Courses_READQueryDtoResourceResponseDto[] paginatedcourse resources
POST/api/admin/courses/:courseId/resourcesAdmin + Courses_CREATECreateResourceDtoResourceResponseDtoat most one of topicId/lessonId
DELETE/api/admin/courses/:courseId/resources/:resourceIdAdmin + Courses_DELETEpath paramsCourseDeleteResponseDtoresource delete
GET/api/admin/courses/:courseId/reviewsAdmin + Courses_READFetchReviewDtoReviewResponseDto[] paginatedfilter hidden, sort by createdAt/rating
POST/api/admin/courses/:courseId/reviewsAdmin + Courses_CREATECreateReviewDtoReviewResponseDtofake reviewer fields (reviewerName, reviewerEmail)
PATCH/api/admin/courses/:courseId/reviews/:reviewIdAdmin + Courses_UPDATEUpdateReviewDtoReviewResponseDtohide/unhide/edit rating/comment/reviewer/image fields
POST/api/admin/courses/:courseId/reviews/:reviewId/upload-imageAdmin + Courses_UPDATEbody with imageUrl + imageRefReviewResponseDtovalidates image payload and updates review
DELETE/api/admin/courses/:courseId/reviews/:reviewIdAdmin + Courses_DELETEpath paramsCourseDeleteResponseDtoreview delete
GET/api/admin/courses/:courseId/quizAdmin + Courses_READcourseId:intQuizResponseDtoquiz config
POST/api/admin/courses/:courseId/quizAdmin + Courses_CREATECreateQuizDtoQuizResponseDtoone quiz per course
PATCH/api/admin/courses/:courseId/quizAdmin + Courses_UPDATEUpdateQuizDtoQuizResponseDtovalidates passMark/attempts/timeLimit
DELETE/api/admin/courses/:courseId/quizAdmin + Courses_DELETEcourseId:intQuizDeleteResponseDtoquiz delete
GET/api/admin/courses/:courseId/quiz/questionsAdmin + Courses_READFetchQuizDtoQuestionResponseDto[] paginatedlist questions
POST/api/admin/courses/:courseId/quiz/questionsAdmin + Courses_CREATECreateQuestionDtoQuestionResponseDtovalidates by questionType
PATCH/api/admin/courses/:courseId/quiz/questions/:questionIdAdmin + Courses_UPDATEUpdateQuestionDtoQuestionResponseDtowhen options or questionType changes, include matching correctAnswer in same request
DELETE/api/admin/courses/:courseId/quiz/questions/:questionIdAdmin + Courses_DELETEpath paramsQuestionDeleteResponseDtoquestion delete
POST/api/admin/courses/:courseId/quiz/questions/reorderAdmin + Courses_UPDATEReorderQuestionsDtoReorderQuestionsResponseDtocomplete sequence required
POST/api/admin/certificates/templatesAdmin + Courses_CREATECreateCertificateTemplateDtoCertificateTemplateResponseDtounique name constraints enforced
GET/api/admin/certificates/templatesAdmin + Courses_READFetchCertificateTemplateDtoCertificateTemplateResponseDto[] paginatedcached list
GET/api/admin/certificates/templates/:templateIdAdmin + Courses_READtemplateId:intCertificateTemplateResponseDtocached detail
PATCH/api/admin/certificates/templates/:templateIdAdmin + Courses_UPDATEUpdateCertificateTemplateDtoCertificateTemplateResponseDtobackgroundImageUrl nullable
DELETE/api/admin/certificates/templates/:templateIdAdmin + Courses_DELETEtemplateId:intTemplateDeleteResponseDtoblocked if referenced
GET/api/admin/certificatesAdmin + Courses_READFetchCertificateDtoCertificateListItemDto[] paginatedissued certificates list
GET/api/coursesOptional authCourseListQueryDtoCourseListItemDto[] paginatedpublished + sellable only
GET/api/courses/featuredOptional authCourseListQueryDtoCourseListItemDto[] paginatedfeatured + published + sellable
GET/api/courses/:slugOptional authslug:stringCourseResponseDtosupports slug history fallback
GET/api/courses/:slug/reviewsOptional authFetchCourseReviewDtoCourseReviewDto[] paginatedvisible reviews only (isHidden=false)
GET/api/courses/:slug/reviews/summaryOptional authslug:stringCourseReviewSummaryDtoreturns totalCount, averageRating, ratingDistribution
GET/api/courses/purchasesUser JWTPurchaseListQueryDtoPurchaseListItemDto[] paginatedpurchased catalog
GET/api/courses/purchases/:purchaseIdUser JWTpurchaseId:intPurchaseDetailDtoownership enforced
GET/api/courses/:courseId/curriculumUser JWTpath paramsCurriculumResponseDtoentitlement required
GET/api/courses/:courseId/lessons/:lessonIdUser JWTpath paramsLessonContentResponseDtoverifies lesson belongs to course
POST/api/courses/:courseId/lessons/:lessonId/completeUser JWTpath paramsLessonCompleteResponseDtoidempotent completion upsert
GET/api/courses/:courseId/progressUser JWTpath paramsCourseProgressResponseDtoper-topic/lesson progress map
GET/api/courses/:courseId/quizUser JWTpath paramsQuizDetailResponseDtoincludes eligibility + attempt stats
POST/api/courses/:courseId/quiz/startUser JWTpath paramsStartQuizResponseDtoreturns in-progress attempt if present
GET/api/courses/:courseId/quiz/attemptsUser JWTQueryDtoQuizAttemptListItemDto[] paginatedattempt history
GET/api/courses/:courseId/quiz/attempt/:attemptIdUser JWTpath paramsAttemptResponseDtodetailed answers/questions
POST/api/courses/:courseId/quiz/attempt/:attemptId/submitUser JWTSubmitQuizDtoSubmitQuizResponseDtoprevents double submit / expired attempt
GET/api/courses/certificatesUser JWTQueryDtoCertificateListItemDto[] paginateduser certificate list
GET/api/courses/:courseId/certificate/eligibilityUser JWTpath paramsCertificateEligibilityResponseDtoexplicit reason codes
POST/api/courses/:courseId/certificate/generateUser JWTpath paramsCertificateGenerationResponseDtoqueue dedupe with jobId=cert:<userId>:<courseId>
GET/api/courses/:courseId/certificateUser JWTpath paramsCertificateResponseDtoreturns cert or domain error
GET/api/certificates/verify/:certificateNumberPubliccertificateNumber:stringCertificateVerifyResponseDtono-auth verification

7.2.1 Quiz Question Update Contract

For PATCH /api/admin/courses/:courseId/quiz/questions/:questionId:

  • If you update options, include correctAnswer in the same payload.
  • If you update questionType, include compatible correctAnswer (and options where required) in the same payload.
  • multiple_choice: correctAnswer must be a string and exactly match one option.
  • true_false: options must resolve to ["true", "false"] and correctAnswer must be "true" or "false".
  • short_answer: correctAnswer can be string or string[]; options should be omitted or null.

Example:

{
  "options": ["Market Order", "Limit Order", "Stop Loss"],
  "correctAnswer": "Market Order",
  "explanation": null
}

7.3 Error Codes

HTTPerrorCodeCondition
400COURSE_TOPIC_CREATE_FAILEDTopic creation failed
400COURSE_LESSON_CREATE_FAILEDLesson creation failed
400COURSE_RESOURCE_CREATE_FAILEDResource creation failed
400COURSE_REVIEW_CREATE_FAILEDReview creation failed
400COURSE_REVIEW_INVALID_RATINGReview rating is outside 1..5
400COURSE_REVIEW_UPLOAD_FAILEDReview image upload payload/operation failed
400COURSE_LINK_FAILEDFailed to create course or product already linked
400COURSE_SELLABLE_REQUIREDProduct type is not 'course'
400COURSE_LESSON_MISMATCHResource belongs to wrong course/topic
400QUIZ_MAX_ATTEMPTS_REACHEDquizAttempts ≤ 0 or user max attempts reached
400QUIZ_INVALID_ANSWER_FORMATInvalid passMark, timeLimit, or answer format
400QUIZ_ANSWER_INVALID_TYPEInvalid question answer type or options
400QUIZ_EXISTS_FOR_COURSEQuiz already exists for this course
400QUIZ_UNABLE_TO_STARTUnable to start quiz attempt
400QUIZ_COURSE_INCOMPLETECourse not completed before quiz
400QUIZ_ALREADY_SUBMITTEDQuiz already submitted
400QUIZ_TIME_LIMIT_EXCEEDEDQuiz time limit exceeded
400QUIZ_ORDER_INDEX_REQUIREDorderIndex missing in request
400QUIZ_REORDER_*Invalid question reorder payload
400CERTIFICATE_CREATE_FAILEDCertificate template creation failed
400CERTIFICATE_TEMPLATE_HAS_PUBLISHED_COURSESTemplate has published courses
400CERTIFICATE_QUEUE_FAILEDCertificate queue job failed
403COURSE_ACCESS_DENIEDUser does not own the resource
403COURSE_ACCESS_EXPIREDCourse access has expired
404COURSE_NOT_FOUNDCourse does not exist
404COURSE_TOPIC_NOT_FOUNDTopic does not exist
404COURSE_LESSON_NOT_FOUNDLesson does not exist
404COURSE_RESOURCE_NOT_FOUNDResource does not exist
404COURSE_REVIEW_NOT_FOUNDReview does not exist
404COURSE_PRODUCT_NOT_FOUNDProduct does not exist
404COURSE_CATEGORY_NOT_FOUNDCategory does not exist
404COURSE_CUSTOMER_NOT_FOUNDCustomer does not exist
404COURSE_PURCHASE_NOT_FOUNDPurchase does not exist
404QUIZ_NOT_FOUNDQuiz does not exist
404QUIZ_ATTEMPT_NOT_FOUNDQuiz attempt does not exist
404QUIZ_QUESTION_NOT_FOUNDQuestion does not exist
404CERTIFICATE_NOT_FOUNDCertificate does not exist
404CERTIFICATE_TEMPLATE_NOT_FOUNDCertificate template does not exist
409COURSE_LINK_FAILEDProduct already linked to another course
429RATE_LIMIT_EXCEEDEDRate limit exceeded
500CERTIFICATE_NUMBER_GENERATION_FAILEDCertificate number generation failed

8. Common Flows / Recipes and Client Integration Guidelines

8.1 Admin Flow: Build Publishable Course

  1. POST /api/admin/courses
  2. POST /api/admin/courses/:courseId/topics
  3. POST /api/admin/courses/:courseId/topics/:topicId/lessons
  4. Optional: add resources/reviews/quiz/template
  5. PATCH /api/admin/courses/:id with status="published", isSellable=true
  6. verify in customer listing via /api/courses

8.2 Mobile Flow: Learn + Quiz + Certificate

  1. List purchases: GET /api/mobile/courses/purchases
  2. Open curriculum: GET /api/mobile/courses/:courseId/curriculum
  3. Mark lessons complete per lesson
  4. Open quiz detail and start/submit attempt
  5. Check eligibility
  6. Generate certificate (queue)
  7. Fetch certificate detail/list and show download URL when available

8.3 Public Verification Flow

  1. Input certificate number
  2. GET /api/certificates/verify/:certificateNumber
  3. If isValid=false, show invalid state
  4. 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, poll GET /certificate with exponential backoff
  • Respect rate limits; centralize 429 handling and retry with jitter.
  • Cache recommendations:
    • /courses list and /courses/:slug detail 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

8.5 Superadmin Panel

  • Course editor should split tabs: Course Info, Curriculum, Resources, Reviews, Quiz, Certificate Template.
  • Lock destructive actions when status is archived unless 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=false for unknown certificate number.
  • 429 behavior 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