> ## Documentation Index
> Fetch the complete documentation index at: https://docs.exode.biz/llms.txt
> Use this file to discover all available pages before exploring further.

# Course and learning

> Structures of the course, progress, lesson and practice objects, based on the shared zod schemas

This page covers the public structures of learning-related objects: `course`, `courseProgress`,
`courseLesson`, `courseLessonPractice`, `courseLessonPracticeAttempt`. They are returned by the course
methods and in webhooks (`CourseCompleted`, `CourseProgressChanged`, `CourseLessonPracticeCompleted`).

<Info>
  The [`course/list/raw`](/en/exode-api/school/course/list) method returns a compact projection
  (`courseId`, `productId`, `name`, `type`, `groupIds`). The full public entity schemas are below.
</Info>

## `course`

Returned by [`course/create`](/en/exode-api/school/course/create),
[`course/:courseId/update`](/en/exode-api/school/course/update), [`course/:courseId/get`](/en/exode-api/school/course/get)
and in course webhooks.

<ResponseField name="id" type="integer" required>Course ID.</ResponseField>
<ResponseField name="type" type="enum" required>Type: `Bundle`, `Webinar`, `TextCourse`, `Assessment`, `VideoCourse`, `PersonalLesson`.</ResponseField>
<ResponseField name="productId" type="integer | null">ID of the [product](/en/exode-api/objects/entities/product) through which the course is sold and access to it is granted.</ResponseField>

<ResponseField name="contentCategoryId" type="integer | null">
  Content category ID. Reserved: not filled in the response in the current version.
</ResponseField>

<ResponseField name="buildStatus" type="enum" required>
  Course build state: `Ready` — the course is ready (the default); `AiGenerating` — the course is still being
  assembled by the AI course wizard; such a course is hidden from everyone except its author.
</ResponseField>

<ResponseField name="name" type="string" required>Name.</ResponseField>
<ResponseField name="description" type="string" required>Description.</ResponseField>
<ResponseField name="alias" type="string | null">Text alias.</ResponseField>
<ResponseField name="tags" type="string[]" required>Tags.</ResponseField>
<ResponseField name="seoTags" type="string[]" required>SEO tags.</ResponseField>
<ResponseField name="image" type="object | null">Images: `{ main }` — URL of the main course image.</ResponseField>
<ResponseField name="promoVideo" type="string | null">Promo video.</ResponseField>
<ResponseField name="settings" type="object" required>Course settings (arbitrary object).</ResponseField>
<ResponseField name="order" type="integer" required>Sort order.</ResponseField>
<ResponseField name="isBundle" type="boolean | null">Whether the course is a bundle.</ResponseField>
<ResponseField name="createdAt" type="string" required>Creation date (ISO 8601).</ResponseField>
<ResponseField name="updatedAt" type="string" required>Update date (ISO 8601).</ResponseField>
<ResponseField name="archivedAt" type="string | null">Archiving date or `null`.</ResponseField>

## `courseProgress`

Returned by the [`course/:courseId/progresses`](/en/exode-api/school/course/progresses) method. A record describes
a user's progress in a specific lesson.

<ResponseField name="id" type="integer" required>Progress record ID.</ResponseField>
<ResponseField name="courseId" type="integer | null">Course ID.</ResponseField>
<ResponseField name="userId" type="integer" required>User ID.</ResponseField>
<ResponseField name="lessonId" type="integer" required>Lesson ID.</ResponseField>

<ResponseField name="status" type="enum | null">
  Lesson status. The completion stages that are stored in the progress record (they also arrive in the
  `CourseProgressChanged` webhook):

  * `NotStarted` — the student has access but has not opened the lesson yet;
  * `OnTheory` — the student is studying the theory;
  * `OnPractice` — the student has opened the practice part;
  * `OnReview` — the assignment has been sent to the curator and is awaiting review;
  * `OnCorrection` — the curator has returned the assignment for revision;
  * `Completed` — the lesson is fully completed.

  The other values describe lesson availability and are computed on the fly: `NotInitialized` (no progress
  record yet), `NotParticipant`, `AvailableAsDemo`, `ExpiredByGroup`, `DisabledByGroup`, `AllowedByProgress`,
  `BlockedByProgress`, `StartScheduled`, `StartScheduledWaitPrev`, `BundleStartBlocked`,
  `BundleStartPendingConfirmation`.
</ResponseField>

<ResponseField name="scheduleStartAt" type="string | null">Scheduled start (ISO 8601).</ResponseField>
<ResponseField name="scheduleFinishAt" type="string | null">Scheduled end (ISO 8601).</ResponseField>
<ResponseField name="practiceDeadlineAt" type="string | null">Practice deadline (ISO 8601).</ResponseField>
<ResponseField name="isCompleted" type="boolean | null">The lesson is completed.</ResponseField>
<ResponseField name="isOnReview" type="boolean | null">Under review.</ResponseField>
<ResponseField name="completedAt" type="string | null">Completion date (ISO 8601).</ResponseField>
<ResponseField name="onReviewAt" type="string | null">Date sent for review (ISO 8601).</ResponseField>
<ResponseField name="statusHistoryLogs" type="object[] | null">Status history: `{ timestamp, status }`.</ResponseField>
<ResponseField name="createdAt" type="string" required>Creation date (ISO 8601).</ResponseField>
<ResponseField name="updatedAt" type="string" required>Update date (ISO 8601).</ResponseField>
<ResponseField name="archivedAt" type="string | null">Archiving date or `null`.</ResponseField>

## `courseLesson`

<ResponseField name="id" type="integer" required>Lesson ID.</ResponseField>
<ResponseField name="courseId" type="integer" required>Course ID.</ResponseField>
<ResponseField name="type" type="enum" required>Type: `Regular`, `Webinar`.</ResponseField>
<ResponseField name="accessType" type="enum" required>Access type: `Demo`, `Participant`.</ResponseField>
<ResponseField name="status" type="string | null">Lesson status.</ResponseField>
<ResponseField name="name" type="string" required>Name.</ResponseField>
<ResponseField name="description" type="string" required>Description.</ResponseField>
<ResponseField name="previewImage" type="string | null">Preview image.</ResponseField>
<ResponseField name="order" type="integer" required>Order.</ResponseField>
<ResponseField name="withContent" type="boolean" required>Has theory content.</ResponseField>
<ResponseField name="withPractice" type="boolean" required>Has a practice part.</ResponseField>
<ResponseField name="publishedAt" type="string | null">Publication date (ISO 8601).</ResponseField>
<ResponseField name="settings" type="object" required>Lesson settings.</ResponseField>
<ResponseField name="isPublished" type="boolean | null">Published.</ResponseField>
<ResponseField name="createdAt" type="string" required>Creation date (ISO 8601).</ResponseField>
<ResponseField name="updatedAt" type="string" required>Update date (ISO 8601).</ResponseField>
<ResponseField name="archivedAt" type="string | null">Archiving date or `null`.</ResponseField>

## `courseLessonPractice`

<ResponseField name="id" type="integer" required>Practice ID.</ResponseField>
<ResponseField name="name" type="string | null">Name.</ResponseField>
<ResponseField name="description" type="string" required>Description.</ResponseField>
<ResponseField name="questionMode" type="enum" required>Question mode: `List`, `Sequence`.</ResponseField>
<ResponseField name="resultMode" type="enum" required>Result mode: `ResultOnly`, `ShowCorrect`, `ShowIncorrect`.</ResponseField>
<ResponseField name="variantMode" type="enum" required>Variant mode: `Random`, `Sequence`.</ResponseField>
<ResponseField name="retryVariantMode" type="enum" required>Variant mode on retry: `Random`, `Sequence`.</ResponseField>
<ResponseField name="maxAttempts" type="integer | null">Maximum number of attempts.</ResponseField>
<ResponseField name="timeLimitInMinutes" type="integer | null">Time limit (minutes).</ResponseField>
<ResponseField name="deadlineInDays" type="integer | null">Deadline (days).</ResponseField>
<ResponseField name="passThreshold" type="integer | null">Pass threshold.</ResponseField>
<ResponseField name="starsPerTaskPoint" type="integer | null">Stars per task point.</ResponseField>
<ResponseField name="requireAllAnswers" type="boolean" required>Answers to all tasks are required.</ResponseField>
<ResponseField name="tasksCount" type="integer" required>Number of tasks.</ResponseField>
<ResponseField name="createdAt" type="string" required>Creation date (ISO 8601).</ResponseField>
<ResponseField name="updatedAt" type="string" required>Update date (ISO 8601).</ResponseField>
<ResponseField name="archivedAt" type="string | null">Archiving date or `null`.</ResponseField>

## `courseLessonPracticeAttempt`

Returned when exporting [practice attempts](/en/exode-api/school/query-export/practice-attempt-find-many) and in the
`CourseLessonPracticeCompleted` webhook.

<ResponseField name="id" type="integer" required>Attempt ID.</ResponseField>
<ResponseField name="uuid" type="string | null">Attempt UUID.</ResponseField>
<ResponseField name="variantId" type="integer" required>Variant ID.</ResponseField>
<ResponseField name="userId" type="integer" required>User ID.</ResponseField>

<ResponseField name="status" type="enum | null">
  Status: `Created`, `OnReview`, `OnCorrection`, `AutoVerified`, `Verified`, `Failed`, `Stacked`.
</ResponseField>

<ResponseField name="order" type="integer" required>Attempt number.</ResponseField>
<ResponseField name="finished" type="boolean" required>The attempt is finished.</ResponseField>
<ResponseField name="sentToReviewAt" type="string | null">Sent for review (ISO 8601).</ResponseField>
<ResponseField name="sentAfterDeadline" type="boolean" required>Sent after the deadline.</ResponseField>
<ResponseField name="deadlineAt" type="string | null">Deadline (ISO 8601).</ResponseField>
<ResponseField name="passedAt" type="string | null">Pass date (ISO 8601).</ResponseField>
<ResponseField name="solvedCount" type="integer" required>Number of solved tasks.</ResponseField>
<ResponseField name="pointsAmount" type="integer" required>Points scored.</ResponseField>
<ResponseField name="maxPointsAmount" type="integer" required>Maximum points.</ResponseField>
<ResponseField name="uncounted" type="boolean" required>The attempt is not counted.</ResponseField>
<ResponseField name="isPassed" type="boolean | null">Passed.</ResponseField>
<ResponseField name="correctPercent" type="number | null">Percentage of correct answers.</ResponseField>
<ResponseField name="isExpired" type="boolean | null">Overdue.</ResponseField>
<ResponseField name="statusHistoryLogs" type="object[] | null">Status history: `{ timestamp, status }`.</ResponseField>
<ResponseField name="createdAt" type="string" required>Creation date (ISO 8601).</ResponseField>
<ResponseField name="updatedAt" type="string" required>Update date (ISO 8601).</ResponseField>
<ResponseField name="archivedAt" type="string | null">Archiving date or `null`.</ResponseField>

***

*Updated: 2026-09-28 05:04 UTC*


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.