TenantAdminCurator

Курсы и контент

Курсы, модули, уроки и контент-блоки — иерархия учебного материала.

POST/api/v1/t/courses

Создать курс

AdminCurator JWT право: manage_courses

Создаёт новый курс в статусе draft. Slug должен быть уникальным в пределах школы. Описание санируется на сервере.

Тело запроса

ПараметрТипОписание
titleобяз.stringНазвание курса (2–255 символов).
slugобяз.stringУникальный slug курса (2–120 символов).Пример: intro-to-go
descriptionопц.stringОписание курса (до 10000 символов, поддерживает rich text).

Пример запроса

cURL
curl -X POST https://api.edumentor.kz/api/v1/t/courses \
  -H "Authorization: Bearer <token>" \
  -H "X-Tenant-Subdomain: acme" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Введение в Go",
    "slug": "intro-to-go",
    "description": "Базовый курс по языку Go."
  }'

Пример ответа

200 OK · application/json
{
  "success": true,
  "data": {
    "id": "c1a2b3c4-...",
    "tenant_id": "8f3b1c2a-...",
    "title": "Введение в Go",
    "slug": "intro-to-go",
    "description": "Базовый курс по языку Go.",
    "status": "draft",
    "sequential_required": false,
    "position": 0,
    "created_at": "2026-05-28T10:00:00Z",
    "updated_at": "2026-05-28T10:00:00Z"
  }
}

Ошибки

КодerrorКогда
400invalid_request_bodyТело запроса не прошло валидацию (например, слишком короткий title или slug).
400duplicate_slugКурс с таким slug уже существует в этой школе.
401unauthorizedТокен отсутствует, истёк или недействителен.
403forbiddenНет права manage_courses.
GET/api/v1/t/courses

Список курсов

Tenant JWT

Постраничный список курсов школы с агрегатами (число учеников, процент завершения, total_blocks — число блоков контента) и счётчиками по статусам для фильтр-пилюль. Каждый курс содержит cover_image_url — готовую ссылку на обложку (свежий presigned GET для cover_image_id; пусто, если обложки нет). Доступен любому авторизованному пользователю; ученики видят только опубликованные курсы (для них counts.draft всегда 0).

Фильтр status принимает только published или draft; любое иное значение тихо игнорируется (как отсутствие фильтра).

Query-параметры

ПараметрТипОписание
pageопц.integerНомер страницы (>= 1). По умолчанию 1.Пример: 1
limitопц.integerРазмер страницы (1–100). По умолчанию 20.Пример: 20
qопц.stringПоиск по названию/описанию курса.
statusопц.stringФильтр по статусу: published или draft.Пример: published
enrolledопц.booleanФильтр для студента: true — только курсы, на которые он записан; false — только не записанные. Игнорируется для админа/куратора.

Пример запроса

cURL
curl "https://api.edumentor.kz/api/v1/t/courses?page=1&limit=20&status=published" \
  -H "Authorization: Bearer <token>" \
  -H "X-Tenant-Subdomain: acme"

Пример ответа

200 OK · application/json
{
  "success": true,
  "data": {
    "courses": [
      {
        "id": "c1a2b3c4-...",
        "title": "Введение в Go",
        "slug": "intro-to-go",
        "status": "published",
        "sequential_required": false,
        "position": 0,
        "cover_image_id": "a9b8c7d6-...",
        "cover_image_url": "https://storage.edumentor.kz/...?X-Amz-Signature=...",
        "auth_has_access": true,
        "students_count": 42,
        "completion_rate": 67,
        "total_blocks": 12,
        "created_at": "2026-05-28T10:00:00Z",
        "updated_at": "2026-05-28T10:00:00Z"
      }
    ],
    "counts": { "all": 12, "published": 8, "draft": 4 },
    "total": 8,
    "page": 1,
    "limit": 20
  }
}

Ошибки

КодerrorКогда
400invalid_paramspage < 1, limit < 1 или limit > 100.
401unauthorizedТокен отсутствует, истёк или недействителен.
GET/api/v1/t/courses/:id

Получить курс

Tenant JWT

Возвращает один курс по идентификатору вместе с производными агрегатами: cover_image_url — готовая ссылка на обложку (свежий presigned GET для cover_image_id), total_blocks — число блоков контента, total_lessons — число уроков, total_students — число записанных студентов. Для зачисленного студента дополнительно приходят progress (его прогресс по курсу) и resume (урок, на котором он остановился). Эти два поля отсутствуют для админа/куратора и для незачисленного студента. Ученики получают только опубликованные курсы; для черновика им вернётся 404.

auth_has_access (boolean) — записан ли текущий студент на курс; для админа/куратора всегда false. progress.total_blocks — снимок на момент последнего обновления прогресса (может отставать от верхнеуровневого total_blocks, который считается «вживую»). resume.lesson_index — сквозной 0-based индекс урока по всему курсу (порядок модуль→урок), удобно показывать «Урок N из M». resume отсутствует, если студент ещё не открывал ни одного блока. Если на курс задано расписание, для студента приходят поля статуса окна: locked (boolean), locked_reason (course_not_started|course_ended), available_from/available_until (ISO8601|null) — см. раздел «Расписание курса».

Параметры пути

ПараметрТипОписание
idобяз.uuidИдентификатор курса.

Пример запроса

cURL
curl https://api.edumentor.kz/api/v1/t/courses/c1a2b3c4-... \
  -H "Authorization: Bearer <token>" \
  -H "X-Tenant-Subdomain: acme"

Пример ответа

200 OK · application/json
{
  "success": true,
  "data": {
    "id": "c1a2b3c4-...",
    "tenant_id": "8f3b1c2a-...",
    "title": "Введение в Go",
    "slug": "intro-to-go",
    "description": "Базовый курс по языку Go.",
    "cover_image_id": "a9b8c7d6-...",
    "cover_image_url": "https://storage.edumentor.kz/...?X-Amz-Signature=...",
    "status": "published",
    "sequential_required": false,
    "position": 0,
    "auth_has_access": true,
    "total_blocks": 12,
    "total_lessons": 4,
    "total_students": 34,
    "progress": {
      "blocks_completed": 5,
      "total_blocks": 12,
      "pct": 41,
      "completed_at": null
    },
    "resume": {
      "lesson_index": 1,
      "module_id": "m1a2b3c4-...",
      "lesson_id": "l1a2b3c4-..."
    },
    "created_at": "2026-05-28T10:00:00Z",
    "updated_at": "2026-05-28T10:00:00Z"
  }
}

Ошибки

КодerrorКогда
400invalid_id_formatid не является корректным UUID.
401unauthorizedТокен отсутствует, истёк или недействителен.
404course_not_foundКурс не найден (или скрыт от ученика как черновик).
PATCH/api/v1/t/courses/:id

Обновить курс

AdminCurator JWT право: manage_courses

Частичное обновление курса. Все поля опциональны — присылайте только изменяемые. Флаг sequential_required включает/выключает последовательное прохождение блоков учениками (админ/куратор обходят гейтинг).

Параметры пути

ПараметрТипОписание
idобяз.uuidИдентификатор курса.

Тело запроса

ПараметрТипОписание
titleопц.stringНовое название (2–255 символов).
slugопц.stringНовый slug (2–120 символов, уникален в школе).
descriptionопц.stringНовое описание (до 10000 символов).
sequential_requiredопц.booleanВключить последовательное прохождение блоков учениками.

Пример запроса

cURL
curl -X PATCH https://api.edumentor.kz/api/v1/t/courses/c1a2b3c4-... \
  -H "Authorization: Bearer <token>" \
  -H "X-Tenant-Subdomain: acme" \
  -H "Content-Type: application/json" \
  -d '{ "title": "Введение в Go (обновлено)", "sequential_required": true }'

Пример ответа

200 OK · application/json
{
  "success": true,
  "data": {
    "id": "c1a2b3c4-...",
    "title": "Введение в Go (обновлено)",
    "slug": "intro-to-go",
    "status": "published",
    "sequential_required": true,
    "updated_at": "2026-05-28T11:00:00Z"
  }
}

Ошибки

КодerrorКогда
400invalid_request_bodyТело запроса не прошло валидацию.
400duplicate_slugКурс с таким slug уже существует в этой школе.
401unauthorizedТокен отсутствует, истёк или недействителен.
403forbiddenНет права manage_courses.
404course_not_foundКурс не найден.
DELETE/api/v1/t/courses/:id

Удалить курс

AdminCurator JWT право: manage_courses

Мягкое удаление курса (soft delete). Если курс используется в активной траектории обучения, удаление блокируется — сначала отвяжите курс от всех траекторий. Успех — 204 No Content без тела.

Параметры пути

ПараметрТипОписание
idобяз.uuidИдентификатор курса.

Пример запроса

cURL
curl -X DELETE https://api.edumentor.kz/api/v1/t/courses/c1a2b3c4-... \
  -H "Authorization: Bearer <token>" \
  -H "X-Tenant-Subdomain: acme"

Пример ответа

200 OK · application/json
204 No Content

Ошибки

КодerrorКогда
400invalid_id_formatid не является корректным UUID.
401unauthorizedТокен отсутствует, истёк или недействителен.
403forbiddenНет права manage_courses.
409course_in_active_pathКурс используется одной или несколькими активными траекториями обучения — сначала отвяжите его.
POST/api/v1/t/courses/:id/publish

Опубликовать курс

AdminCurator JWT право: manage_courses

Переводит курс в статус published. Идемпотентно. Возвращает новый статус.

Параметры пути

ПараметрТипОписание
idобяз.uuidИдентификатор курса.

Пример запроса

cURL
curl -X POST https://api.edumentor.kz/api/v1/t/courses/c1a2b3c4-.../publish \
  -H "Authorization: Bearer <token>" \
  -H "X-Tenant-Subdomain: acme"

Пример ответа

200 OK · application/json
{
  "success": true,
  "data": { "status": "published" }
}

Ошибки

КодerrorКогда
401unauthorizedТокен отсутствует, истёк или недействителен.
403forbiddenНет права manage_courses.
404course_not_foundКурс не найден.
POST/api/v1/t/courses/:id/unpublish

Снять курс с публикации

AdminCurator JWT право: manage_courses

Переводит курс обратно в статус draft. Идемпотентно. Возвращает новый статус.

Параметры пути

ПараметрТипОписание
idобяз.uuidИдентификатор курса.

Пример запроса

cURL
curl -X POST https://api.edumentor.kz/api/v1/t/courses/c1a2b3c4-.../unpublish \
  -H "Authorization: Bearer <token>" \
  -H "X-Tenant-Subdomain: acme"

Пример ответа

200 OK · application/json
{
  "success": true,
  "data": { "status": "draft" }
}

Ошибки

КодerrorКогда
401unauthorizedТокен отсутствует, истёк или недействителен.
403forbiddenНет права manage_courses.
404course_not_foundКурс не найден.
PATCH/api/v1/t/courses/:id/cover-image

Установить обложку курса

AdminCurator JWT право: manage_courses

Привязывает к курсу медиа-актив в качестве обложки. cover_image_id должен ссылаться на существующий актив той же школы.

Параметры пути

ПараметрТипОписание
idобяз.uuidИдентификатор курса.

Тело запроса

ПараметрТипОписание
cover_image_idобяз.uuidИдентификатор медиа-актива для обложки.

Пример запроса

cURL
curl -X PATCH https://api.edumentor.kz/api/v1/t/courses/c1a2b3c4-.../cover-image \
  -H "Authorization: Bearer <token>" \
  -H "X-Tenant-Subdomain: acme" \
  -H "Content-Type: application/json" \
  -d '{ "cover_image_id": "a9b8c7d6-..." }'

Пример ответа

200 OK · application/json
{
  "success": true,
  "data": { "cover_image_id": "a9b8c7d6-..." }
}

Ошибки

КодerrorКогда
400invalid_request_bodyТело запроса не прошло валидацию.
400invalid_cover_imageМедиа-актив не найден, не является изображением или принадлежит другой школе.
401unauthorizedТокен отсутствует, истёк или недействителен.
403forbiddenНет права manage_courses.
404course_not_foundКурс не найден.
POST/api/v1/t/courses/:id/duplicate

Дублировать курс

AdminCurator JWT право: manage_courses

Создаёт глубокую копию курса вместе с не-удалёнными модулями, уроками и контент-блоками. Тело запроса пустое. Новый курс всегда в статусе draft; название = исходное + (копия), slug = исходный + -copy (с суффиксами -copy-2, -copy-3 при коллизии). Ссылки на медиа/тесты/задания копируются по ссылке (та же школа).

Параметры пути

ПараметрТипОписание
idобяз.uuidИдентификатор исходного курса.

Пример запроса

cURL
curl -X POST https://api.edumentor.kz/api/v1/t/courses/c1a2b3c4-.../duplicate \
  -H "Authorization: Bearer <token>" \
  -H "X-Tenant-Subdomain: acme"

Пример ответа

200 OK · application/json
{
  "success": true,
  "data": {
    "id": "d2e3f4a5-...",
    "title": "Введение в Go (копия)",
    "slug": "intro-to-go-copy",
    "status": "draft",
    "sequential_required": false,
    "created_at": "2026-05-28T12:00:00Z",
    "updated_at": "2026-05-28T12:00:00Z"
  }
}

Ошибки

КодerrorКогда
400invalid_id_formatid не является корректным UUID.
401unauthorizedТокен отсутствует, истёк или недействителен.
403forbiddenНет права manage_courses.
404course_not_foundИсходный курс не найден или принадлежит другой школе.
POST/api/v1/t/courses/bulk-publish

Массовая публикация курсов

AdminCurator JWT право: manage_courses

Публикует список курсов одним запросом. Идемпотентно и ограничено школой: уже опубликованные, чужие (другой школы) и удалённые ID тихо пропускаются (счётчик skipped).

Тело запроса

ПараметрТипОписание
idsобяз.string[]Массив UUID курсов (от 1 до 100).

Пример запроса

cURL
curl -X POST https://api.edumentor.kz/api/v1/t/courses/bulk-publish \
  -H "Authorization: Bearer <token>" \
  -H "X-Tenant-Subdomain: acme" \
  -H "Content-Type: application/json" \
  -d '{ "ids": ["c1a2b3c4-...", "d2e3f4a5-..."] }'

Пример ответа

200 OK · application/json
{
  "success": true,
  "data": { "affected": 2, "skipped": 0 }
}

Ошибки

КодerrorКогда
400invalid_paramsПустой массив, более 100 ID или некорректный UUID.
401unauthorizedТокен отсутствует, истёк или недействителен.
403forbiddenНет права manage_courses.
POST/api/v1/t/courses/bulk-unpublish

Массовое снятие курсов с публикации

AdminCurator JWT право: manage_courses

Симметрично массовой публикации: переводит список курсов из published в draft. Идемпотентно и ограничено школой; неподходящие ID пропускаются.

Тело запроса

ПараметрТипОписание
idsобяз.string[]Массив UUID курсов (от 1 до 100).

Пример запроса

cURL
curl -X POST https://api.edumentor.kz/api/v1/t/courses/bulk-unpublish \
  -H "Authorization: Bearer <token>" \
  -H "X-Tenant-Subdomain: acme" \
  -H "Content-Type: application/json" \
  -d '{ "ids": ["c1a2b3c4-...", "d2e3f4a5-..."] }'

Пример ответа

200 OK · application/json
{
  "success": true,
  "data": { "affected": 2, "skipped": 0 }
}

Ошибки

КодerrorКогда
400invalid_paramsПустой массив, более 100 ID или некорректный UUID.
401unauthorizedТокен отсутствует, истёк или недействителен.
403forbiddenНет права manage_courses.
POST/api/v1/t/courses/:id/modules

Создать модуль

AdminCurator JWT право: manage_courses

Создаёт модуль внутри курса. Позиция назначается автоматически (в конец списка). Описание санируется на сервере.

Параметры пути

ПараметрТипОписание
idобяз.uuidИдентификатор курса.

Тело запроса

ПараметрТипОписание
titleобяз.stringНазвание модуля (1–255 символов).
descriptionопц.stringОписание модуля (до 10000 символов).

Пример запроса

cURL
curl -X POST https://api.edumentor.kz/api/v1/t/courses/c1a2b3c4-.../modules \
  -H "Authorization: Bearer <token>" \
  -H "X-Tenant-Subdomain: acme" \
  -H "Content-Type: application/json" \
  -d '{ "title": "Модуль 1. Основы", "description": "Синтаксис и типы." }'

Пример ответа

200 OK · application/json
{
  "success": true,
  "data": {
    "id": "m1a2b3c4-...",
    "course_id": "c1a2b3c4-...",
    "tenant_id": "8f3b1c2a-...",
    "title": "Модуль 1. Основы",
    "description": "Синтаксис и типы.",
    "position": 0,
    "created_at": "2026-05-28T10:05:00Z",
    "updated_at": "2026-05-28T10:05:00Z"
  }
}

Ошибки

КодerrorКогда
400invalid_request_bodyТело запроса не прошло валидацию.
401unauthorizedТокен отсутствует, истёк или недействителен.
403forbiddenНет права manage_courses.
GET/api/v1/t/courses/:id/modules

Список модулей курса

Tenant JWT

Возвращает модули курса, упорядоченные по полю position, в конверте { modules, total_lessons }. Каждый модуль дополнен полем lessons_count (число не-удалённых уроков) и массивом lessons — упорядоченными уроками модуля. Каждый урок содержит passed (пройден ли урок студентом) и облегчённый список blocks (только id/type/position/payload — без presigned-ссылок и встроенных quiz/assignment). total_lessons — сумма уроков по всему курсу. Доступно любому авторизованному пользователю школы; студент видит только доступные ему по группам уроки.

passed = у урока есть хотя бы один блок И все его блоки завершены студентом; для админа/куратора и пустого урока всегда false. Блоки типа quiz_ref дополнительно возвращают question_count (integer) — число вопросов в привязанном тесте. Это эндпоинт-оглавление: для iframe-блока ссылка лежит в payload, но presigned-URL видео/PDF и встроенные quiz/assignment отдаёт только GET /api/v1/t/lessons/:id/blocks. Если задано расписание, модули и уроки несут поля статуса окна для студента (locked, locked_reason, available_from, available_until); у полностью закрытого по дате урока payload блоков вырезается, чтобы не раскрывать контент. См. раздел «Расписание курса».

Параметры пути

ПараметрТипОписание
idобяз.uuidИдентификатор курса.

Пример запроса

cURL
curl https://api.edumentor.kz/api/v1/t/courses/c1a2b3c4-.../modules \
  -H "Authorization: Bearer <token>" \
  -H "X-Tenant-Subdomain: acme"

Пример ответа

200 OK · application/json
{
  "success": true,
  "data": {
    "modules": [
      {
        "id": "m1a2b3c4-...",
        "course_id": "c1a2b3c4-...",
        "title": "Модуль 1. Основы",
        "description": "Синтаксис и типы.",
        "position": 0,
        "lessons_count": 4,
        "lessons": [
          {
            "id": "l1a2b3c4-...",
            "module_id": "m1a2b3c4-...",
            "title": "Урок 1. Установка",
            "position": 0,
            "passed": true,
            "blocks": [
              {
                "id": "b1a2b3c4-...",
                "type": "video",
                "position": 0,
                "payload": { "title": "Лекция 1", "duration_seconds": 615 }
              },
              {
                "id": "b2a2b3c4-...",
                "type": "quiz_ref",
                "position": 1,
                "payload": {},
                "question_count": 5
              }
            ]
          }
        ],
        "created_at": "2026-05-28T10:05:00Z",
        "updated_at": "2026-05-28T10:05:00Z"
      }
    ],
    "total_lessons": 4
  }
}

Ошибки

КодerrorКогда
400invalid_id_formatid не является корректным UUID.
401unauthorizedТокен отсутствует, истёк или недействителен.
GET/api/v1/t/modules/:id

Получить модуль

Tenant JWT

Возвращает один модуль по идентификатору.

Параметры пути

ПараметрТипОписание
idобяз.uuidИдентификатор модуля.

Пример запроса

cURL
curl https://api.edumentor.kz/api/v1/t/modules/m1a2b3c4-... \
  -H "Authorization: Bearer <token>" \
  -H "X-Tenant-Subdomain: acme"

Пример ответа

200 OK · application/json
{
  "success": true,
  "data": {
    "id": "m1a2b3c4-...",
    "course_id": "c1a2b3c4-...",
    "title": "Модуль 1. Основы",
    "description": "Синтаксис и типы.",
    "position": 0,
    "created_at": "2026-05-28T10:05:00Z",
    "updated_at": "2026-05-28T10:05:00Z"
  }
}

Ошибки

КодerrorКогда
400invalid_id_formatid не является корректным UUID.
401unauthorizedТокен отсутствует, истёк или недействителен.
404module_not_foundМодуль не найден.
PATCH/api/v1/t/modules/:id

Обновить модуль

AdminCurator JWT право: manage_courses

Частичное обновление модуля. Все поля опциональны — присылайте только изменяемые.

Параметры пути

ПараметрТипОписание
idобяз.uuidИдентификатор модуля.

Тело запроса

ПараметрТипОписание
titleопц.stringНовое название (1–255 символов).
descriptionопц.stringНовое описание (до 10000 символов).

Пример запроса

cURL
curl -X PATCH https://api.edumentor.kz/api/v1/t/modules/m1a2b3c4-... \
  -H "Authorization: Bearer <token>" \
  -H "X-Tenant-Subdomain: acme" \
  -H "Content-Type: application/json" \
  -d '{ "title": "Модуль 1. Введение" }'

Пример ответа

200 OK · application/json
{
  "success": true,
  "data": {
    "id": "m1a2b3c4-...",
    "course_id": "c1a2b3c4-...",
    "title": "Модуль 1. Введение",
    "position": 0,
    "updated_at": "2026-05-28T11:10:00Z"
  }
}

Ошибки

КодerrorКогда
400invalid_request_bodyТело запроса не прошло валидацию.
401unauthorizedТокен отсутствует, истёк или недействителен.
403forbiddenНет права manage_courses.
404module_not_foundМодуль не найден.
DELETE/api/v1/t/modules/:id

Удалить модуль

AdminCurator JWT право: manage_courses

Мягкое удаление модуля (soft delete). Успех — 204 No Content без тела.

Параметры пути

ПараметрТипОписание
idобяз.uuidИдентификатор модуля.

Пример запроса

cURL
curl -X DELETE https://api.edumentor.kz/api/v1/t/modules/m1a2b3c4-... \
  -H "Authorization: Bearer <token>" \
  -H "X-Tenant-Subdomain: acme"

Пример ответа

200 OK · application/json
204 No Content

Ошибки

КодerrorКогда
400invalid_id_formatid не является корректным UUID.
401unauthorizedТокен отсутствует, истёк или недействителен.
403forbiddenНет права manage_courses.
PATCH/api/v1/t/courses/:id/reorder-modules

Переупорядочить модули

AdminCurator JWT право: manage_courses

Задаёт новый порядок модулей внутри курса (drag-and-drop). Массив ordered_ids должен содержать полный набор идентификаторов модулей курса без дубликатов и пропусков.

Параметры пути

ПараметрТипОписание
idобяз.uuidИдентификатор курса.

Тело запроса

ПараметрТипОписание
ordered_idsобяз.string[]Полный список UUID модулей в желаемом порядке (минимум 1).

Пример запроса

cURL
curl -X PATCH https://api.edumentor.kz/api/v1/t/courses/c1a2b3c4-.../reorder-modules \
  -H "Authorization: Bearer <token>" \
  -H "X-Tenant-Subdomain: acme" \
  -H "Content-Type: application/json" \
  -d '{ "ordered_ids": ["m2-...", "m1-...", "m3-..."] }'

Пример ответа

200 OK · application/json
{
  "success": true,
  "data": { "reordered": 3 }
}

Ошибки

КодerrorКогда
400invalid_request_bodyТело запроса не прошло валидацию (пустой массив или некорректный UUID).
401unauthorizedТокен отсутствует, истёк или недействителен.
403forbiddenНет права manage_courses.
409reorder_duplicateВ ordered_ids есть повторяющиеся идентификаторы.
409reorder_incompleteСписок не совпадает с полным набором модулей курса (пропуски или лишние ID).
PATCH/api/v1/t/modules/:id/cover-image

Установить обложку модуля

AdminCurator JWT право: manage_courses

Привязывает к модулю медиа-актив в качестве обложки. cover_image_id должен ссылаться на существующий актив той же школы.

Параметры пути

ПараметрТипОписание
idобяз.uuidИдентификатор модуля.

Тело запроса

ПараметрТипОписание
cover_image_idобяз.uuidИдентификатор медиа-актива для обложки.

Пример запроса

cURL
curl -X PATCH https://api.edumentor.kz/api/v1/t/modules/m1a2b3c4-.../cover-image \
  -H "Authorization: Bearer <token>" \
  -H "X-Tenant-Subdomain: acme" \
  -H "Content-Type: application/json" \
  -d '{ "cover_image_id": "a9b8c7d6-..." }'

Пример ответа

200 OK · application/json
{
  "success": true,
  "data": { "cover_image_id": "a9b8c7d6-..." }
}

Ошибки

КодerrorКогда
400invalid_request_bodyТело запроса не прошло валидацию.
400invalid_cover_imageМедиа-актив не найден или принадлежит другой школе.
401unauthorizedТокен отсутствует, истёк или недействителен.
403forbiddenНет права manage_courses.
404module_not_foundМодуль не найден.
POST/api/v1/t/modules/:id/lessons

Создать урок

AdminCurator JWT право: manage_courses

Создаёт урок внутри модуля. Позиция назначается автоматически (в конец списка). Описание (rich text) санируется на сервере.

Параметры пути

ПараметрТипОписание
idобяз.uuidИдентификатор модуля.

Тело запроса

ПараметрТипОписание
titleобяз.stringНазвание урока (1–255 символов).
descriptionопц.stringОписание урока (rich text, до 50000 символов).

Пример запроса

cURL
curl -X POST https://api.edumentor.kz/api/v1/t/modules/m1a2b3c4-.../lessons \
  -H "Authorization: Bearer <token>" \
  -H "X-Tenant-Subdomain: acme" \
  -H "Content-Type: application/json" \
  -d '{ "title": "Урок 1. Hello, World", "description": "<p>Первая программа.</p>" }'

Пример ответа

200 OK · application/json
{
  "success": true,
  "data": {
    "id": "l1a2b3c4-...",
    "module_id": "m1a2b3c4-...",
    "tenant_id": "8f3b1c2a-...",
    "title": "Урок 1. Hello, World",
    "description": "<p>Первая программа.</p>",
    "position": 0,
    "created_at": "2026-05-28T10:10:00Z",
    "updated_at": "2026-05-28T10:10:00Z"
  }
}

Ошибки

КодerrorКогда
400invalid_request_bodyТело запроса не прошло валидацию.
401unauthorizedТокен отсутствует, истёк или недействителен.
403forbiddenНет права manage_courses.
GET/api/v1/t/modules/:id/lessons

Список уроков модуля

Tenant JWT

Возвращает уроки модуля, упорядоченные по полю position. Доступно любому авторизованному пользователю школы. Поле data — массив уроков.

Параметры пути

ПараметрТипОписание
idобяз.uuidИдентификатор модуля.

Пример запроса

cURL
curl https://api.edumentor.kz/api/v1/t/modules/m1a2b3c4-.../lessons \
  -H "Authorization: Bearer <token>" \
  -H "X-Tenant-Subdomain: acme"

Пример ответа

200 OK · application/json
{
  "success": true,
  "data": [
    {
      "id": "l1a2b3c4-...",
      "module_id": "m1a2b3c4-...",
      "title": "Урок 1. Hello, World",
      "description": "<p>Первая программа.</p>",
      "position": 0,
      "created_at": "2026-05-28T10:10:00Z",
      "updated_at": "2026-05-28T10:10:00Z"
    }
  ]
}

Ошибки

КодerrorКогда
400invalid_id_formatid не является корректным UUID.
401unauthorizedТокен отсутствует, истёк или недействителен.
GET/api/v1/t/lessons/:id

Получить урок

Tenant JWT

Возвращает один урок по идентификатору.

Параметры пути

ПараметрТипОписание
idобяз.uuidИдентификатор урока.

Пример запроса

cURL
curl https://api.edumentor.kz/api/v1/t/lessons/l1a2b3c4-... \
  -H "Authorization: Bearer <token>" \
  -H "X-Tenant-Subdomain: acme"

Пример ответа

200 OK · application/json
{
  "success": true,
  "data": {
    "id": "l1a2b3c4-...",
    "module_id": "m1a2b3c4-...",
    "title": "Урок 1. Hello, World",
    "description": "<p>Первая программа.</p>",
    "position": 0,
    "created_at": "2026-05-28T10:10:00Z",
    "updated_at": "2026-05-28T10:10:00Z"
  }
}

Ошибки

КодerrorКогда
400invalid_id_formatid не является корректным UUID.
401unauthorizedТокен отсутствует, истёк или недействителен.
404lesson_not_foundУрок не найден.
PATCH/api/v1/t/lessons/:id

Обновить урок

AdminCurator JWT право: manage_courses

Частичное обновление урока. Все поля опциональны. Описание (rich text) санируется на сервере.

Параметры пути

ПараметрТипОписание
idобяз.uuidИдентификатор урока.

Тело запроса

ПараметрТипОписание
titleопц.stringНовое название (1–255 символов).
descriptionопц.stringНовое описание (rich text, до 50000 символов).

Пример запроса

cURL
curl -X PATCH https://api.edumentor.kz/api/v1/t/lessons/l1a2b3c4-... \
  -H "Authorization: Bearer <token>" \
  -H "X-Tenant-Subdomain: acme" \
  -H "Content-Type: application/json" \
  -d '{ "title": "Урок 1. Первая программа" }'

Пример ответа

200 OK · application/json
{
  "success": true,
  "data": {
    "id": "l1a2b3c4-...",
    "module_id": "m1a2b3c4-...",
    "title": "Урок 1. Первая программа",
    "position": 0,
    "updated_at": "2026-05-28T11:15:00Z"
  }
}

Ошибки

КодerrorКогда
400invalid_request_bodyТело запроса не прошло валидацию.
401unauthorizedТокен отсутствует, истёк или недействителен.
403forbiddenНет права manage_courses.
404lesson_not_foundУрок не найден.
DELETE/api/v1/t/lessons/:id

Удалить урок

AdminCurator JWT право: manage_courses

Мягкое удаление урока (soft delete). Успех — 204 No Content без тела.

Параметры пути

ПараметрТипОписание
idобяз.uuidИдентификатор урока.

Пример запроса

cURL
curl -X DELETE https://api.edumentor.kz/api/v1/t/lessons/l1a2b3c4-... \
  -H "Authorization: Bearer <token>" \
  -H "X-Tenant-Subdomain: acme"

Пример ответа

200 OK · application/json
204 No Content

Ошибки

КодerrorКогда
400invalid_id_formatid не является корректным UUID.
401unauthorizedТокен отсутствует, истёк или недействителен.
403forbiddenНет права manage_courses.
PATCH/api/v1/t/modules/:id/reorder-lessons

Переупорядочить уроки

AdminCurator JWT право: manage_courses

Задаёт новый порядок уроков внутри модуля (drag-and-drop). Массив ordered_ids должен содержать полный набор идентификаторов уроков модуля без дубликатов и пропусков.

Параметры пути

ПараметрТипОписание
idобяз.uuidИдентификатор модуля.

Тело запроса

ПараметрТипОписание
ordered_idsобяз.string[]Полный список UUID уроков в желаемом порядке (минимум 1).

Пример запроса

cURL
curl -X PATCH https://api.edumentor.kz/api/v1/t/modules/m1a2b3c4-.../reorder-lessons \
  -H "Authorization: Bearer <token>" \
  -H "X-Tenant-Subdomain: acme" \
  -H "Content-Type: application/json" \
  -d '{ "ordered_ids": ["l2-...", "l1-..."] }'

Пример ответа

200 OK · application/json
{
  "success": true,
  "data": { "reordered": 2 }
}

Ошибки

КодerrorКогда
400invalid_request_bodyТело запроса не прошло валидацию (пустой массив или некорректный UUID).
401unauthorizedТокен отсутствует, истёк или недействителен.
403forbiddenНет права manage_courses.
409reorder_duplicateВ ordered_ids есть повторяющиеся идентификаторы.
409reorder_incompleteСписок не совпадает с полным набором уроков модуля.
PATCH/api/v1/t/lessons/:id/cover-image

Установить обложку урока

AdminCurator JWT право: manage_courses

Привязывает к уроку медиа-актив в качестве обложки. cover_image_id должен ссылаться на существующий актив той же школы.

Параметры пути

ПараметрТипОписание
idобяз.uuidИдентификатор урока.

Тело запроса

ПараметрТипОписание
cover_image_idобяз.uuidИдентификатор медиа-актива для обложки.

Пример запроса

cURL
curl -X PATCH https://api.edumentor.kz/api/v1/t/lessons/l1a2b3c4-.../cover-image \
  -H "Authorization: Bearer <token>" \
  -H "X-Tenant-Subdomain: acme" \
  -H "Content-Type: application/json" \
  -d '{ "cover_image_id": "a9b8c7d6-..." }'

Пример ответа

200 OK · application/json
{
  "success": true,
  "data": { "cover_image_id": "a9b8c7d6-..." }
}

Ошибки

КодerrorКогда
400invalid_request_bodyТело запроса не прошло валидацию.
400invalid_cover_imageМедиа-актив не найден или принадлежит другой школе.
401unauthorizedТокен отсутствует, истёк или недействителен.
403forbiddenНет права manage_courses.
404lesson_not_foundУрок не найден.
GET/api/v1/t/lessons/:id/groups

Группы доступа урока

AdminCurator JWT право: manage_courses

Возвращает список UUID групп, которым ограничен доступ к уроку. Пустой массив означает, что урок доступен всем зачисленным студентам.

Параметры пути

ПараметрТипОписание
idобяз.uuidИдентификатор урока.

Пример запроса

cURL
curl https://api.edumentor.kz/api/v1/t/lessons/l1a2b3c4-.../groups \
  -H "Authorization: Bearer <token>" \
  -H "X-Tenant-Subdomain: acme"

Пример ответа

200 OK · application/json
{
  "success": true,
  "data": { "group_ids": ["g1a2b3c4-...", "g2b3c4d5-..."] }
}

Ошибки

КодerrorКогда
400invalid_id_formatid не является корректным UUID.
401unauthorizedТокен отсутствует, истёк или недействителен.
403forbiddenНет права manage_courses.
404lesson_not_foundУрок не найден.
PUT/api/v1/t/lessons/:id/groups

Задать группы доступа урока

AdminCurator JWT право: manage_courses

Перезаписывает список групп, которым доступен урок. Если задана хотя бы одна группа — урок виден только студентам из этих групп; пустой массив снимает все ограничения (урок снова доступен всем зачисленным).

Параметры пути

ПараметрТипОписание
idобяз.uuidИдентификатор урока.

Тело запроса

ПараметрТипОписание
group_idsобяз.string[]Список UUID групп (пустой массив — снять ограничения).

Пример запроса

cURL
curl -X PUT https://api.edumentor.kz/api/v1/t/lessons/l1a2b3c4-.../groups \
  -H "Authorization: Bearer <token>" \
  -H "X-Tenant-Subdomain: acme" \
  -H "Content-Type: application/json" \
  -d '{ "group_ids": ["g1a2b3c4-..."] }'

Пример ответа

200 OK · application/json
{
  "success": true,
  "data": { "group_ids": ["g1a2b3c4-..."] }
}

Ошибки

КодerrorКогда
400invalid_id_formatid не является корректным UUID.
400invalid_request_bodyТело запроса не прошло валидацию.
401unauthorizedТокен отсутствует, истёк или недействителен.
403forbiddenНет права manage_courses.
404lesson_not_foundУрок не найден.
POST/api/v1/t/lessons/:id/blocks

Создать контент-блок

AdminCurator JWT право: manage_courses

Добавляет в урок контент-блок. Поле type — один из: video, iframe, text, pdf, quiz_ref, assignment_ref (тип неизменяем после создания). payload — объект, форма которого зависит от типа (см. ниже). FK-колонки media_asset_id (для video/pdf), quiz_id (для quiz_ref), assignment_id (для assignment_ref) обязательны для соответствующих типов. Позиция назначается автоматически.

Формы payload по типам: video → { title, duration_seconds, alt_text } + media_asset_id; iframe → { url, title } (provider вычисляется сервером, url канонизируется и проверяется по allowlist хостов); text → { html } (HTML санируется через bluemonday UGCPolicy); pdf → { title, total_pages } + media_asset_id; quiz_ref → {} + quiz_id; assignment_ref → {} + assignment_id. Для video-блоков duration_seconds и для pdf-блоков total_pages можно НЕ указывать — бэкенд извлечёт их автоматически после загрузки медиа; если клиент прислал значение (> 0), используется оно (приоритет у клиента).

Параметры пути

ПараметрТипОписание
idобяз.uuidИдентификатор урока.

Тело запроса

ПараметрТипОписание
typeобяз.stringТип блока: video, iframe, text, pdf, quiz_ref или assignment_ref.Пример: video
payloadобяз.objectСодержимое блока; форма зависит от type (см. примечание).
media_asset_idопц.uuidМедиа-актив. Обязателен для type=video и type=pdf.
quiz_idопц.uuidТест. Обязателен для type=quiz_ref.
assignment_idопц.uuidЗадание. Обязателен для type=assignment_ref.

Пример запроса

cURL
curl -X POST https://api.edumentor.kz/api/v1/t/lessons/l1a2b3c4-.../blocks \
  -H "Authorization: Bearer <token>" \
  -H "X-Tenant-Subdomain: acme" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "video",
    "media_asset_id": "a9b8c7d6-...",
    "payload": {
      "title": "Лекция 1",
      "duration_seconds": 615,
      "alt_text": "Видеолекция о синтаксисе Go"
    }
  }'

Пример ответа

200 OK · application/json
{
  "success": true,
  "data": {
    "id": "b1a2b3c4-...",
    "lesson_id": "l1a2b3c4-...",
    "tenant_id": "8f3b1c2a-...",
    "type": "video",
    "position": 0,
    "payload": {
      "title": "Лекция 1",
      "duration_seconds": 615,
      "alt_text": "Видеолекция о синтаксисе Go"
    },
    "media_asset_id": "a9b8c7d6-...",
    "created_at": "2026-05-28T10:20:00Z",
    "updated_at": "2026-05-28T10:20:00Z"
  }
}

Ошибки

КодerrorКогда
400invalid_request_bodyТело запроса не прошло базовую валидацию.
400unknown_block_typetype не входит в список допустимых.
400invalid_content_block_payloadpayload не соответствует объявленному типу или нарушено соответствие тип↔FK.
401unauthorizedТокен отсутствует, истёк или недействителен.
403forbiddenНет права manage_courses.
404lesson_not_foundУрок не найден.
GET/api/v1/t/lessons/:id/blocks

Список контент-блоков урока

Tenant JWT

Возвращает контент-блоки урока в порядке position. Для video/pdf блоков добавляется свежий presigned-URL (media_url). Для блоков quiz_ref/assignment_ref встраивается полная карточка теста (quiz) или задания (assignment) — чтобы клиент отрисовал содержимое одним запросом. Поле payload — типизированный объект (форма зависит от type). Поле data — массив блоков.

Для учеников при включённом sequential_required на курсе заблокированные блоки возвращаются с locked: true, а поля payload/media_url/quiz_id/assignment_id/media_asset_id вырезаются на сервере. Для админа/куратора locked всегда false. Заблокированные блоки дополнительно возвращают locked_summary — краткое описание без контента: title (для всех типов), question_count (для quiz_ref), total_pages (для pdf), duration_seconds (для video); набор полей зависит от типа блока, поэтому отсутствующие для данного типа поля просто не приходят. Блокировка по РАСПИСАНИЮ работает так же (тот же locked: true + вырезание полей), но дополнительно несёт locked_reason (*_not_started|*_ended) и available_from/available_until — клиент показывает «Откроется …» / «Доступ закрыт …». Для тестов/заданий после дедлайна блок остаётся открытым, если студент начал попытку до дедлайна (правило «первой попытки»).

Параметры пути

ПараметрТипОписание
idобяз.uuidИдентификатор урока.

Пример запроса

cURL
curl https://api.edumentor.kz/api/v1/t/lessons/l1a2b3c4-.../blocks \
  -H "Authorization: Bearer <token>" \
  -H "X-Tenant-Subdomain: acme"

Пример ответа

200 OK · application/json
{
  "success": true,
  "data": [
    {
      "id": "b1a2b3c4-...",
      "lesson_id": "l1a2b3c4-...",
      "type": "video",
      "position": 0,
      "payload": {
        "title": "Лекция 1",
        "duration_seconds": 615,
        "alt_text": "Видеолекция о синтаксисе Go"
      },
      "media_asset_id": "a9b8c7d6-...",
      "media_url": "https://minio.edumentor.kz/...&X-Amz-Signature=...",
      "locked": false
    },
    {
      "id": "b2a2b3c4-...",
      "lesson_id": "l1a2b3c4-...",
      "type": "quiz_ref",
      "position": 1,
      "locked": true,
      "locked_summary": {
        "title": "Итоговый тест",
        "question_count": 10
      }
    }
  ]
}

Ошибки

КодerrorКогда
400invalid_id_formatid не является корректным UUID.
401unauthorizedТокен отсутствует, истёк или недействителен.
GET/api/v1/t/blocks/:id

Получить контент-блок

Tenant JWT

Возвращает один контент-блок с типизированным payload. Для video/pdf добавляется свежий presigned-URL (media_url). Для quiz_ref/assignment_ref встраивается полная карточка теста (quiz) или задания (assignment).

Если ученику блок недоступен по последовательному гейтингу — возвращается 403 block_locked. Если блок недоступен по расписанию (урок вне окна дат, и правило «первой попытки» не применимо) — 403 schedule_window_closed. Для админа/куратора гейтинг не применяется.

Параметры пути

ПараметрТипОписание
idобяз.uuidИдентификатор контент-блока.

Пример запроса

cURL
curl https://api.edumentor.kz/api/v1/t/blocks/b1a2b3c4-... \
  -H "Authorization: Bearer <token>" \
  -H "X-Tenant-Subdomain: acme"

Пример ответа

200 OK · application/json
{
  "success": true,
  "data": {
    "id": "b1a2b3c4-...",
    "lesson_id": "l1a2b3c4-...",
    "type": "text",
    "position": 1,
    "payload": { "html": "<p>Конспект урока.</p>" },
    "locked": false
  }
}

Ошибки

КодerrorКогда
400invalid_id_formatid не является корректным UUID.
401unauthorizedТокен отсутствует, истёк или недействителен.
403block_lockedУченику блок недоступен по правилам последовательного прохождения.
403schedule_window_closedУрок блока вне окна доступа по расписанию (и правило «первой попытки» не применимо).
404block_not_foundБлок не найден.
PATCH/api/v1/t/blocks/:id

Обновить контент-блок

AdminCurator JWT право: manage_courses

Частичное обновление блока. Тип блока неизменяем — менять можно payload и media_asset_id. payload валидируется и санируется как при создании, исходя из исходного типа блока.

Параметры пути

ПараметрТипОписание
idобяз.uuidИдентификатор контент-блока.

Тело запроса

ПараметрТипОписание
payloadопц.objectНовое содержимое блока; форма зависит от исходного типа.
media_asset_idопц.uuidНовый медиа-актив (для video/pdf).

Пример запроса

cURL
curl -X PATCH https://api.edumentor.kz/api/v1/t/blocks/b1a2b3c4-... \
  -H "Authorization: Bearer <token>" \
  -H "X-Tenant-Subdomain: acme" \
  -H "Content-Type: application/json" \
  -d '{ "payload": { "html": "<p>Обновлённый конспект.</p>" } }'

Пример ответа

200 OK · application/json
{
  "success": true,
  "data": {
    "id": "b1a2b3c4-...",
    "lesson_id": "l1a2b3c4-...",
    "type": "text",
    "position": 1,
    "payload": { "html": "<p>Обновлённый конспект.</p>" },
    "updated_at": "2026-05-28T11:30:00Z"
  }
}

Ошибки

КодerrorКогда
400invalid_request_bodyТело запроса не прошло базовую валидацию.
400invalid_content_block_payloadpayload не соответствует типу блока.
401unauthorizedТокен отсутствует, истёк или недействителен.
403forbiddenНет права manage_courses.
404block_not_foundБлок не найден.
DELETE/api/v1/t/blocks/:id

Удалить контент-блок

AdminCurator JWT право: manage_courses

Мягкое удаление контент-блока (soft delete). Успех — 204 No Content без тела.

Параметры пути

ПараметрТипОписание
idобяз.uuidИдентификатор контент-блока.

Пример запроса

cURL
curl -X DELETE https://api.edumentor.kz/api/v1/t/blocks/b1a2b3c4-... \
  -H "Authorization: Bearer <token>" \
  -H "X-Tenant-Subdomain: acme"

Пример ответа

200 OK · application/json
204 No Content

Ошибки

КодerrorКогда
400invalid_id_formatid не является корректным UUID.
401unauthorizedТокен отсутствует, истёк или недействителен.
403forbiddenНет права manage_courses.
PATCH/api/v1/t/lessons/:id/reorder-blocks

Переупорядочить контент-блоки

AdminCurator JWT право: manage_courses

Задаёт новый порядок контент-блоков внутри урока (drag-and-drop). Массив ordered_ids должен содержать полный набор идентификаторов блоков урока без дубликатов и пропусков.

Параметры пути

ПараметрТипОписание
idобяз.uuidИдентификатор урока.

Тело запроса

ПараметрТипОписание
ordered_idsобяз.string[]Полный список UUID блоков в желаемом порядке (минимум 1).

Пример запроса

cURL
curl -X PATCH https://api.edumentor.kz/api/v1/t/lessons/l1a2b3c4-.../reorder-blocks \
  -H "Authorization: Bearer <token>" \
  -H "X-Tenant-Subdomain: acme" \
  -H "Content-Type: application/json" \
  -d '{ "ordered_ids": ["b2-...", "b1-...", "b3-..."] }'

Пример ответа

200 OK · application/json
{
  "success": true,
  "data": { "reordered": 3 }
}

Ошибки

КодerrorКогда
400invalid_request_bodyТело запроса не прошло валидацию (пустой массив или некорректный UUID).
401unauthorizedТокен отсутствует, истёк или недействителен.
403forbiddenНет права manage_courses.
409reorder_duplicateВ ordered_ids есть повторяющиеся идентификаторы.
409reorder_incompleteСписок не совпадает с полным набором блоков урока.