Курсы и контент
Курсы, модули, уроки и контент-блоки — иерархия учебного материала.
- POST
/api/v1/t/coursesСоздать курс - GET
/api/v1/t/coursesСписок курсов - GET
/api/v1/t/courses/:idПолучить курс - PATCH
/api/v1/t/courses/:idОбновить курс - DELETE
/api/v1/t/courses/:idУдалить курс - POST
/api/v1/t/courses/:id/publishОпубликовать курс - POST
/api/v1/t/courses/:id/unpublishСнять курс с публикации - PATCH
/api/v1/t/courses/:id/cover-imageУстановить обложку курса - POST
/api/v1/t/courses/:id/duplicateДублировать курс - POST
/api/v1/t/courses/bulk-publishМассовая публикация курсов - POST
/api/v1/t/courses/bulk-unpublishМассовое снятие курсов с публикации - POST
/api/v1/t/courses/:id/modulesСоздать модуль - GET
/api/v1/t/courses/:id/modulesСписок модулей курса - GET
/api/v1/t/modules/:idПолучить модуль - PATCH
/api/v1/t/modules/:idОбновить модуль - DELETE
/api/v1/t/modules/:idУдалить модуль - PATCH
/api/v1/t/courses/:id/reorder-modulesПереупорядочить модули - PATCH
/api/v1/t/modules/:id/cover-imageУстановить обложку модуля - POST
/api/v1/t/modules/:id/lessonsСоздать урок - GET
/api/v1/t/modules/:id/lessonsСписок уроков модуля - GET
/api/v1/t/lessons/:idПолучить урок - PATCH
/api/v1/t/lessons/:idОбновить урок - DELETE
/api/v1/t/lessons/:idУдалить урок - PATCH
/api/v1/t/modules/:id/reorder-lessonsПереупорядочить уроки - PATCH
/api/v1/t/lessons/:id/cover-imageУстановить обложку урока - GET
/api/v1/t/lessons/:id/groupsГруппы доступа урока - PUT
/api/v1/t/lessons/:id/groupsЗадать группы доступа урока - POST
/api/v1/t/lessons/:id/blocksСоздать контент-блок - GET
/api/v1/t/lessons/:id/blocksСписок контент-блоков урока - GET
/api/v1/t/blocks/:idПолучить контент-блок - PATCH
/api/v1/t/blocks/:idОбновить контент-блок - DELETE
/api/v1/t/blocks/:idУдалить контент-блок - PATCH
/api/v1/t/lessons/:id/reorder-blocksПереупорядочить контент-блоки
/api/v1/t/coursesСоздать курс
Создаёт новый курс в статусе draft. Slug должен быть уникальным в пределах школы. Описание санируется на сервере.
Тело запроса
| Параметр | Тип | Описание |
|---|---|---|
titleобяз. | string | Название курса (2–255 символов). |
slugобяз. | string | Уникальный slug курса (2–120 символов).Пример: intro-to-go |
descriptionопц. | string | Описание курса (до 10000 символов, поддерживает rich text). |
Пример запроса
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."
}'Пример ответа
{
"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 | Когда |
|---|---|---|
| 400 | invalid_request_body | Тело запроса не прошло валидацию (например, слишком короткий title или slug). |
| 400 | duplicate_slug | Курс с таким slug уже существует в этой школе. |
| 401 | unauthorized | Токен отсутствует, истёк или недействителен. |
| 403 | forbidden | Нет права manage_courses. |
/api/v1/t/coursesСписок курсов
Постраничный список курсов школы с агрегатами (число учеников, процент завершения, 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 "https://api.edumentor.kz/api/v1/t/courses?page=1&limit=20&status=published" \
-H "Authorization: Bearer <token>" \
-H "X-Tenant-Subdomain: acme"Пример ответа
{
"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 | Когда |
|---|---|---|
| 400 | invalid_params | page < 1, limit < 1 или limit > 100. |
| 401 | unauthorized | Токен отсутствует, истёк или недействителен. |
/api/v1/t/courses/:idПолучить курс
Возвращает один курс по идентификатору вместе с производными агрегатами: 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 https://api.edumentor.kz/api/v1/t/courses/c1a2b3c4-... \
-H "Authorization: Bearer <token>" \
-H "X-Tenant-Subdomain: acme"Пример ответа
{
"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 | Когда |
|---|---|---|
| 400 | invalid_id_format | id не является корректным UUID. |
| 401 | unauthorized | Токен отсутствует, истёк или недействителен. |
| 404 | course_not_found | Курс не найден (или скрыт от ученика как черновик). |
/api/v1/t/courses/:idОбновить курс
Частичное обновление курса. Все поля опциональны — присылайте только изменяемые. Флаг sequential_required включает/выключает последовательное прохождение блоков учениками (админ/куратор обходят гейтинг).
Параметры пути
| Параметр | Тип | Описание |
|---|---|---|
idобяз. | uuid | Идентификатор курса. |
Тело запроса
| Параметр | Тип | Описание |
|---|---|---|
titleопц. | string | Новое название (2–255 символов). |
slugопц. | string | Новый slug (2–120 символов, уникален в школе). |
descriptionопц. | string | Новое описание (до 10000 символов). |
sequential_requiredопц. | boolean | Включить последовательное прохождение блоков учениками. |
Пример запроса
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 }'Пример ответа
{
"success": true,
"data": {
"id": "c1a2b3c4-...",
"title": "Введение в Go (обновлено)",
"slug": "intro-to-go",
"status": "published",
"sequential_required": true,
"updated_at": "2026-05-28T11:00:00Z"
}
}Ошибки
| Код | error | Когда |
|---|---|---|
| 400 | invalid_request_body | Тело запроса не прошло валидацию. |
| 400 | duplicate_slug | Курс с таким slug уже существует в этой школе. |
| 401 | unauthorized | Токен отсутствует, истёк или недействителен. |
| 403 | forbidden | Нет права manage_courses. |
| 404 | course_not_found | Курс не найден. |
/api/v1/t/courses/:idУдалить курс
Мягкое удаление курса (soft delete). Если курс используется в активной траектории обучения, удаление блокируется — сначала отвяжите курс от всех траекторий. Успех — 204 No Content без тела.
Параметры пути
| Параметр | Тип | Описание |
|---|---|---|
idобяз. | uuid | Идентификатор курса. |
Пример запроса
curl -X DELETE https://api.edumentor.kz/api/v1/t/courses/c1a2b3c4-... \
-H "Authorization: Bearer <token>" \
-H "X-Tenant-Subdomain: acme"Пример ответа
204 No ContentОшибки
| Код | error | Когда |
|---|---|---|
| 400 | invalid_id_format | id не является корректным UUID. |
| 401 | unauthorized | Токен отсутствует, истёк или недействителен. |
| 403 | forbidden | Нет права manage_courses. |
| 409 | course_in_active_path | Курс используется одной или несколькими активными траекториями обучения — сначала отвяжите его. |
/api/v1/t/courses/:id/publishОпубликовать курс
Переводит курс в статус published. Идемпотентно. Возвращает новый статус.
Параметры пути
| Параметр | Тип | Описание |
|---|---|---|
idобяз. | uuid | Идентификатор курса. |
Пример запроса
curl -X POST https://api.edumentor.kz/api/v1/t/courses/c1a2b3c4-.../publish \
-H "Authorization: Bearer <token>" \
-H "X-Tenant-Subdomain: acme"Пример ответа
{
"success": true,
"data": { "status": "published" }
}Ошибки
| Код | error | Когда |
|---|---|---|
| 401 | unauthorized | Токен отсутствует, истёк или недействителен. |
| 403 | forbidden | Нет права manage_courses. |
| 404 | course_not_found | Курс не найден. |
/api/v1/t/courses/:id/unpublishСнять курс с публикации
Переводит курс обратно в статус draft. Идемпотентно. Возвращает новый статус.
Параметры пути
| Параметр | Тип | Описание |
|---|---|---|
idобяз. | uuid | Идентификатор курса. |
Пример запроса
curl -X POST https://api.edumentor.kz/api/v1/t/courses/c1a2b3c4-.../unpublish \
-H "Authorization: Bearer <token>" \
-H "X-Tenant-Subdomain: acme"Пример ответа
{
"success": true,
"data": { "status": "draft" }
}Ошибки
| Код | error | Когда |
|---|---|---|
| 401 | unauthorized | Токен отсутствует, истёк или недействителен. |
| 403 | forbidden | Нет права manage_courses. |
| 404 | course_not_found | Курс не найден. |
/api/v1/t/courses/:id/cover-imageУстановить обложку курса
Привязывает к курсу медиа-актив в качестве обложки. cover_image_id должен ссылаться на существующий актив той же школы.
Параметры пути
| Параметр | Тип | Описание |
|---|---|---|
idобяз. | uuid | Идентификатор курса. |
Тело запроса
| Параметр | Тип | Описание |
|---|---|---|
cover_image_idобяз. | uuid | Идентификатор медиа-актива для обложки. |
Пример запроса
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-..." }'Пример ответа
{
"success": true,
"data": { "cover_image_id": "a9b8c7d6-..." }
}Ошибки
| Код | error | Когда |
|---|---|---|
| 400 | invalid_request_body | Тело запроса не прошло валидацию. |
| 400 | invalid_cover_image | Медиа-актив не найден, не является изображением или принадлежит другой школе. |
| 401 | unauthorized | Токен отсутствует, истёк или недействителен. |
| 403 | forbidden | Нет права manage_courses. |
| 404 | course_not_found | Курс не найден. |
/api/v1/t/courses/:id/duplicateДублировать курс
Создаёт глубокую копию курса вместе с не-удалёнными модулями, уроками и контент-блоками. Тело запроса пустое. Новый курс всегда в статусе draft; название = исходное + (копия), slug = исходный + -copy (с суффиксами -copy-2, -copy-3 при коллизии). Ссылки на медиа/тесты/задания копируются по ссылке (та же школа).
Параметры пути
| Параметр | Тип | Описание |
|---|---|---|
idобяз. | uuid | Идентификатор исходного курса. |
Пример запроса
curl -X POST https://api.edumentor.kz/api/v1/t/courses/c1a2b3c4-.../duplicate \
-H "Authorization: Bearer <token>" \
-H "X-Tenant-Subdomain: acme"Пример ответа
{
"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 | Когда |
|---|---|---|
| 400 | invalid_id_format | id не является корректным UUID. |
| 401 | unauthorized | Токен отсутствует, истёк или недействителен. |
| 403 | forbidden | Нет права manage_courses. |
| 404 | course_not_found | Исходный курс не найден или принадлежит другой школе. |
/api/v1/t/courses/bulk-publishМассовая публикация курсов
Публикует список курсов одним запросом. Идемпотентно и ограничено школой: уже опубликованные, чужие (другой школы) и удалённые ID тихо пропускаются (счётчик skipped).
Тело запроса
| Параметр | Тип | Описание |
|---|---|---|
idsобяз. | string[] | Массив UUID курсов (от 1 до 100). |
Пример запроса
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-..."] }'Пример ответа
{
"success": true,
"data": { "affected": 2, "skipped": 0 }
}Ошибки
| Код | error | Когда |
|---|---|---|
| 400 | invalid_params | Пустой массив, более 100 ID или некорректный UUID. |
| 401 | unauthorized | Токен отсутствует, истёк или недействителен. |
| 403 | forbidden | Нет права manage_courses. |
/api/v1/t/courses/bulk-unpublishМассовое снятие курсов с публикации
Симметрично массовой публикации: переводит список курсов из published в draft. Идемпотентно и ограничено школой; неподходящие ID пропускаются.
Тело запроса
| Параметр | Тип | Описание |
|---|---|---|
idsобяз. | string[] | Массив UUID курсов (от 1 до 100). |
Пример запроса
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-..."] }'Пример ответа
{
"success": true,
"data": { "affected": 2, "skipped": 0 }
}Ошибки
| Код | error | Когда |
|---|---|---|
| 400 | invalid_params | Пустой массив, более 100 ID или некорректный UUID. |
| 401 | unauthorized | Токен отсутствует, истёк или недействителен. |
| 403 | forbidden | Нет права manage_courses. |
/api/v1/t/courses/:id/modulesСоздать модуль
Создаёт модуль внутри курса. Позиция назначается автоматически (в конец списка). Описание санируется на сервере.
Параметры пути
| Параметр | Тип | Описание |
|---|---|---|
idобяз. | uuid | Идентификатор курса. |
Тело запроса
| Параметр | Тип | Описание |
|---|---|---|
titleобяз. | string | Название модуля (1–255 символов). |
descriptionопц. | string | Описание модуля (до 10000 символов). |
Пример запроса
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": "Синтаксис и типы." }'Пример ответа
{
"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 | Когда |
|---|---|---|
| 400 | invalid_request_body | Тело запроса не прошло валидацию. |
| 401 | unauthorized | Токен отсутствует, истёк или недействителен. |
| 403 | forbidden | Нет права manage_courses. |
/api/v1/t/courses/:id/modulesСписок модулей курса
Возвращает модули курса, упорядоченные по полю 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 https://api.edumentor.kz/api/v1/t/courses/c1a2b3c4-.../modules \
-H "Authorization: Bearer <token>" \
-H "X-Tenant-Subdomain: acme"Пример ответа
{
"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 | Когда |
|---|---|---|
| 400 | invalid_id_format | id не является корректным UUID. |
| 401 | unauthorized | Токен отсутствует, истёк или недействителен. |
/api/v1/t/modules/:idПолучить модуль
Возвращает один модуль по идентификатору.
Параметры пути
| Параметр | Тип | Описание |
|---|---|---|
idобяз. | uuid | Идентификатор модуля. |
Пример запроса
curl https://api.edumentor.kz/api/v1/t/modules/m1a2b3c4-... \
-H "Authorization: Bearer <token>" \
-H "X-Tenant-Subdomain: acme"Пример ответа
{
"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 | Когда |
|---|---|---|
| 400 | invalid_id_format | id не является корректным UUID. |
| 401 | unauthorized | Токен отсутствует, истёк или недействителен. |
| 404 | module_not_found | Модуль не найден. |
/api/v1/t/modules/:idОбновить модуль
Частичное обновление модуля. Все поля опциональны — присылайте только изменяемые.
Параметры пути
| Параметр | Тип | Описание |
|---|---|---|
idобяз. | uuid | Идентификатор модуля. |
Тело запроса
| Параметр | Тип | Описание |
|---|---|---|
titleопц. | string | Новое название (1–255 символов). |
descriptionопц. | string | Новое описание (до 10000 символов). |
Пример запроса
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. Введение" }'Пример ответа
{
"success": true,
"data": {
"id": "m1a2b3c4-...",
"course_id": "c1a2b3c4-...",
"title": "Модуль 1. Введение",
"position": 0,
"updated_at": "2026-05-28T11:10:00Z"
}
}Ошибки
| Код | error | Когда |
|---|---|---|
| 400 | invalid_request_body | Тело запроса не прошло валидацию. |
| 401 | unauthorized | Токен отсутствует, истёк или недействителен. |
| 403 | forbidden | Нет права manage_courses. |
| 404 | module_not_found | Модуль не найден. |
/api/v1/t/modules/:idУдалить модуль
Мягкое удаление модуля (soft delete). Успех — 204 No Content без тела.
Параметры пути
| Параметр | Тип | Описание |
|---|---|---|
idобяз. | uuid | Идентификатор модуля. |
Пример запроса
curl -X DELETE https://api.edumentor.kz/api/v1/t/modules/m1a2b3c4-... \
-H "Authorization: Bearer <token>" \
-H "X-Tenant-Subdomain: acme"Пример ответа
204 No ContentОшибки
| Код | error | Когда |
|---|---|---|
| 400 | invalid_id_format | id не является корректным UUID. |
| 401 | unauthorized | Токен отсутствует, истёк или недействителен. |
| 403 | forbidden | Нет права manage_courses. |
/api/v1/t/courses/:id/reorder-modulesПереупорядочить модули
Задаёт новый порядок модулей внутри курса (drag-and-drop). Массив ordered_ids должен содержать полный набор идентификаторов модулей курса без дубликатов и пропусков.
Параметры пути
| Параметр | Тип | Описание |
|---|---|---|
idобяз. | uuid | Идентификатор курса. |
Тело запроса
| Параметр | Тип | Описание |
|---|---|---|
ordered_idsобяз. | string[] | Полный список UUID модулей в желаемом порядке (минимум 1). |
Пример запроса
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-..."] }'Пример ответа
{
"success": true,
"data": { "reordered": 3 }
}Ошибки
| Код | error | Когда |
|---|---|---|
| 400 | invalid_request_body | Тело запроса не прошло валидацию (пустой массив или некорректный UUID). |
| 401 | unauthorized | Токен отсутствует, истёк или недействителен. |
| 403 | forbidden | Нет права manage_courses. |
| 409 | reorder_duplicate | В ordered_ids есть повторяющиеся идентификаторы. |
| 409 | reorder_incomplete | Список не совпадает с полным набором модулей курса (пропуски или лишние ID). |
/api/v1/t/modules/:id/cover-imageУстановить обложку модуля
Привязывает к модулю медиа-актив в качестве обложки. cover_image_id должен ссылаться на существующий актив той же школы.
Параметры пути
| Параметр | Тип | Описание |
|---|---|---|
idобяз. | uuid | Идентификатор модуля. |
Тело запроса
| Параметр | Тип | Описание |
|---|---|---|
cover_image_idобяз. | uuid | Идентификатор медиа-актива для обложки. |
Пример запроса
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-..." }'Пример ответа
{
"success": true,
"data": { "cover_image_id": "a9b8c7d6-..." }
}Ошибки
| Код | error | Когда |
|---|---|---|
| 400 | invalid_request_body | Тело запроса не прошло валидацию. |
| 400 | invalid_cover_image | Медиа-актив не найден или принадлежит другой школе. |
| 401 | unauthorized | Токен отсутствует, истёк или недействителен. |
| 403 | forbidden | Нет права manage_courses. |
| 404 | module_not_found | Модуль не найден. |
/api/v1/t/modules/:id/lessonsСоздать урок
Создаёт урок внутри модуля. Позиция назначается автоматически (в конец списка). Описание (rich text) санируется на сервере.
Параметры пути
| Параметр | Тип | Описание |
|---|---|---|
idобяз. | uuid | Идентификатор модуля. |
Тело запроса
| Параметр | Тип | Описание |
|---|---|---|
titleобяз. | string | Название урока (1–255 символов). |
descriptionопц. | string | Описание урока (rich text, до 50000 символов). |
Пример запроса
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>" }'Пример ответа
{
"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 | Когда |
|---|---|---|
| 400 | invalid_request_body | Тело запроса не прошло валидацию. |
| 401 | unauthorized | Токен отсутствует, истёк или недействителен. |
| 403 | forbidden | Нет права manage_courses. |
/api/v1/t/modules/:id/lessonsСписок уроков модуля
Возвращает уроки модуля, упорядоченные по полю position. Доступно любому авторизованному пользователю школы. Поле data — массив уроков.
Параметры пути
| Параметр | Тип | Описание |
|---|---|---|
idобяз. | uuid | Идентификатор модуля. |
Пример запроса
curl https://api.edumentor.kz/api/v1/t/modules/m1a2b3c4-.../lessons \
-H "Authorization: Bearer <token>" \
-H "X-Tenant-Subdomain: acme"Пример ответа
{
"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 | Когда |
|---|---|---|
| 400 | invalid_id_format | id не является корректным UUID. |
| 401 | unauthorized | Токен отсутствует, истёк или недействителен. |
/api/v1/t/lessons/:idПолучить урок
Возвращает один урок по идентификатору.
Параметры пути
| Параметр | Тип | Описание |
|---|---|---|
idобяз. | uuid | Идентификатор урока. |
Пример запроса
curl https://api.edumentor.kz/api/v1/t/lessons/l1a2b3c4-... \
-H "Authorization: Bearer <token>" \
-H "X-Tenant-Subdomain: acme"Пример ответа
{
"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 | Когда |
|---|---|---|
| 400 | invalid_id_format | id не является корректным UUID. |
| 401 | unauthorized | Токен отсутствует, истёк или недействителен. |
| 404 | lesson_not_found | Урок не найден. |
/api/v1/t/lessons/:idОбновить урок
Частичное обновление урока. Все поля опциональны. Описание (rich text) санируется на сервере.
Параметры пути
| Параметр | Тип | Описание |
|---|---|---|
idобяз. | uuid | Идентификатор урока. |
Тело запроса
| Параметр | Тип | Описание |
|---|---|---|
titleопц. | string | Новое название (1–255 символов). |
descriptionопц. | string | Новое описание (rich text, до 50000 символов). |
Пример запроса
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. Первая программа" }'Пример ответа
{
"success": true,
"data": {
"id": "l1a2b3c4-...",
"module_id": "m1a2b3c4-...",
"title": "Урок 1. Первая программа",
"position": 0,
"updated_at": "2026-05-28T11:15:00Z"
}
}Ошибки
| Код | error | Когда |
|---|---|---|
| 400 | invalid_request_body | Тело запроса не прошло валидацию. |
| 401 | unauthorized | Токен отсутствует, истёк или недействителен. |
| 403 | forbidden | Нет права manage_courses. |
| 404 | lesson_not_found | Урок не найден. |
/api/v1/t/lessons/:idУдалить урок
Мягкое удаление урока (soft delete). Успех — 204 No Content без тела.
Параметры пути
| Параметр | Тип | Описание |
|---|---|---|
idобяз. | uuid | Идентификатор урока. |
Пример запроса
curl -X DELETE https://api.edumentor.kz/api/v1/t/lessons/l1a2b3c4-... \
-H "Authorization: Bearer <token>" \
-H "X-Tenant-Subdomain: acme"Пример ответа
204 No ContentОшибки
| Код | error | Когда |
|---|---|---|
| 400 | invalid_id_format | id не является корректным UUID. |
| 401 | unauthorized | Токен отсутствует, истёк или недействителен. |
| 403 | forbidden | Нет права manage_courses. |
/api/v1/t/modules/:id/reorder-lessonsПереупорядочить уроки
Задаёт новый порядок уроков внутри модуля (drag-and-drop). Массив ordered_ids должен содержать полный набор идентификаторов уроков модуля без дубликатов и пропусков.
Параметры пути
| Параметр | Тип | Описание |
|---|---|---|
idобяз. | uuid | Идентификатор модуля. |
Тело запроса
| Параметр | Тип | Описание |
|---|---|---|
ordered_idsобяз. | string[] | Полный список UUID уроков в желаемом порядке (минимум 1). |
Пример запроса
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-..."] }'Пример ответа
{
"success": true,
"data": { "reordered": 2 }
}Ошибки
| Код | error | Когда |
|---|---|---|
| 400 | invalid_request_body | Тело запроса не прошло валидацию (пустой массив или некорректный UUID). |
| 401 | unauthorized | Токен отсутствует, истёк или недействителен. |
| 403 | forbidden | Нет права manage_courses. |
| 409 | reorder_duplicate | В ordered_ids есть повторяющиеся идентификаторы. |
| 409 | reorder_incomplete | Список не совпадает с полным набором уроков модуля. |
/api/v1/t/lessons/:id/cover-imageУстановить обложку урока
Привязывает к уроку медиа-актив в качестве обложки. cover_image_id должен ссылаться на существующий актив той же школы.
Параметры пути
| Параметр | Тип | Описание |
|---|---|---|
idобяз. | uuid | Идентификатор урока. |
Тело запроса
| Параметр | Тип | Описание |
|---|---|---|
cover_image_idобяз. | uuid | Идентификатор медиа-актива для обложки. |
Пример запроса
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-..." }'Пример ответа
{
"success": true,
"data": { "cover_image_id": "a9b8c7d6-..." }
}Ошибки
| Код | error | Когда |
|---|---|---|
| 400 | invalid_request_body | Тело запроса не прошло валидацию. |
| 400 | invalid_cover_image | Медиа-актив не найден или принадлежит другой школе. |
| 401 | unauthorized | Токен отсутствует, истёк или недействителен. |
| 403 | forbidden | Нет права manage_courses. |
| 404 | lesson_not_found | Урок не найден. |
/api/v1/t/lessons/:id/groupsГруппы доступа урока
Возвращает список UUID групп, которым ограничен доступ к уроку. Пустой массив означает, что урок доступен всем зачисленным студентам.
Параметры пути
| Параметр | Тип | Описание |
|---|---|---|
idобяз. | uuid | Идентификатор урока. |
Пример запроса
curl https://api.edumentor.kz/api/v1/t/lessons/l1a2b3c4-.../groups \
-H "Authorization: Bearer <token>" \
-H "X-Tenant-Subdomain: acme"Пример ответа
{
"success": true,
"data": { "group_ids": ["g1a2b3c4-...", "g2b3c4d5-..."] }
}Ошибки
| Код | error | Когда |
|---|---|---|
| 400 | invalid_id_format | id не является корректным UUID. |
| 401 | unauthorized | Токен отсутствует, истёк или недействителен. |
| 403 | forbidden | Нет права manage_courses. |
| 404 | lesson_not_found | Урок не найден. |
/api/v1/t/lessons/:id/groupsЗадать группы доступа урока
Перезаписывает список групп, которым доступен урок. Если задана хотя бы одна группа — урок виден только студентам из этих групп; пустой массив снимает все ограничения (урок снова доступен всем зачисленным).
Параметры пути
| Параметр | Тип | Описание |
|---|---|---|
idобяз. | uuid | Идентификатор урока. |
Тело запроса
| Параметр | Тип | Описание |
|---|---|---|
group_idsобяз. | string[] | Список UUID групп (пустой массив — снять ограничения). |
Пример запроса
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-..."] }'Пример ответа
{
"success": true,
"data": { "group_ids": ["g1a2b3c4-..."] }
}Ошибки
| Код | error | Когда |
|---|---|---|
| 400 | invalid_id_format | id не является корректным UUID. |
| 400 | invalid_request_body | Тело запроса не прошло валидацию. |
| 401 | unauthorized | Токен отсутствует, истёк или недействителен. |
| 403 | forbidden | Нет права manage_courses. |
| 404 | lesson_not_found | Урок не найден. |
/api/v1/t/lessons/:id/blocksСоздать контент-блок
Добавляет в урок контент-блок. Поле 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 -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"
}
}'Пример ответа
{
"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 | Когда |
|---|---|---|
| 400 | invalid_request_body | Тело запроса не прошло базовую валидацию. |
| 400 | unknown_block_type | type не входит в список допустимых. |
| 400 | invalid_content_block_payload | payload не соответствует объявленному типу или нарушено соответствие тип↔FK. |
| 401 | unauthorized | Токен отсутствует, истёк или недействителен. |
| 403 | forbidden | Нет права manage_courses. |
| 404 | lesson_not_found | Урок не найден. |
/api/v1/t/lessons/:id/blocksСписок контент-блоков урока
Возвращает контент-блоки урока в порядке 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 https://api.edumentor.kz/api/v1/t/lessons/l1a2b3c4-.../blocks \
-H "Authorization: Bearer <token>" \
-H "X-Tenant-Subdomain: acme"Пример ответа
{
"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 | Когда |
|---|---|---|
| 400 | invalid_id_format | id не является корректным UUID. |
| 401 | unauthorized | Токен отсутствует, истёк или недействителен. |
/api/v1/t/blocks/:idПолучить контент-блок
Возвращает один контент-блок с типизированным payload. Для video/pdf добавляется свежий presigned-URL (media_url). Для quiz_ref/assignment_ref встраивается полная карточка теста (quiz) или задания (assignment).
Если ученику блок недоступен по последовательному гейтингу — возвращается 403 block_locked. Если блок недоступен по расписанию (урок вне окна дат, и правило «первой попытки» не применимо) — 403 schedule_window_closed. Для админа/куратора гейтинг не применяется.
Параметры пути
| Параметр | Тип | Описание |
|---|---|---|
idобяз. | uuid | Идентификатор контент-блока. |
Пример запроса
curl https://api.edumentor.kz/api/v1/t/blocks/b1a2b3c4-... \
-H "Authorization: Bearer <token>" \
-H "X-Tenant-Subdomain: acme"Пример ответа
{
"success": true,
"data": {
"id": "b1a2b3c4-...",
"lesson_id": "l1a2b3c4-...",
"type": "text",
"position": 1,
"payload": { "html": "<p>Конспект урока.</p>" },
"locked": false
}
}Ошибки
| Код | error | Когда |
|---|---|---|
| 400 | invalid_id_format | id не является корректным UUID. |
| 401 | unauthorized | Токен отсутствует, истёк или недействителен. |
| 403 | block_locked | Ученику блок недоступен по правилам последовательного прохождения. |
| 403 | schedule_window_closed | Урок блока вне окна доступа по расписанию (и правило «первой попытки» не применимо). |
| 404 | block_not_found | Блок не найден. |
/api/v1/t/blocks/:idОбновить контент-блок
Частичное обновление блока. Тип блока неизменяем — менять можно payload и media_asset_id. payload валидируется и санируется как при создании, исходя из исходного типа блока.
Параметры пути
| Параметр | Тип | Описание |
|---|---|---|
idобяз. | uuid | Идентификатор контент-блока. |
Тело запроса
| Параметр | Тип | Описание |
|---|---|---|
payloadопц. | object | Новое содержимое блока; форма зависит от исходного типа. |
media_asset_idопц. | uuid | Новый медиа-актив (для video/pdf). |
Пример запроса
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>" } }'Пример ответа
{
"success": true,
"data": {
"id": "b1a2b3c4-...",
"lesson_id": "l1a2b3c4-...",
"type": "text",
"position": 1,
"payload": { "html": "<p>Обновлённый конспект.</p>" },
"updated_at": "2026-05-28T11:30:00Z"
}
}Ошибки
| Код | error | Когда |
|---|---|---|
| 400 | invalid_request_body | Тело запроса не прошло базовую валидацию. |
| 400 | invalid_content_block_payload | payload не соответствует типу блока. |
| 401 | unauthorized | Токен отсутствует, истёк или недействителен. |
| 403 | forbidden | Нет права manage_courses. |
| 404 | block_not_found | Блок не найден. |
/api/v1/t/blocks/:idУдалить контент-блок
Мягкое удаление контент-блока (soft delete). Успех — 204 No Content без тела.
Параметры пути
| Параметр | Тип | Описание |
|---|---|---|
idобяз. | uuid | Идентификатор контент-блока. |
Пример запроса
curl -X DELETE https://api.edumentor.kz/api/v1/t/blocks/b1a2b3c4-... \
-H "Authorization: Bearer <token>" \
-H "X-Tenant-Subdomain: acme"Пример ответа
204 No ContentОшибки
| Код | error | Когда |
|---|---|---|
| 400 | invalid_id_format | id не является корректным UUID. |
| 401 | unauthorized | Токен отсутствует, истёк или недействителен. |
| 403 | forbidden | Нет права manage_courses. |
/api/v1/t/lessons/:id/reorder-blocksПереупорядочить контент-блоки
Задаёт новый порядок контент-блоков внутри урока (drag-and-drop). Массив ordered_ids должен содержать полный набор идентификаторов блоков урока без дубликатов и пропусков.
Параметры пути
| Параметр | Тип | Описание |
|---|---|---|
idобяз. | uuid | Идентификатор урока. |
Тело запроса
| Параметр | Тип | Описание |
|---|---|---|
ordered_idsобяз. | string[] | Полный список UUID блоков в желаемом порядке (минимум 1). |
Пример запроса
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-..."] }'Пример ответа
{
"success": true,
"data": { "reordered": 3 }
}Ошибки
| Код | error | Когда |
|---|---|---|
| 400 | invalid_request_body | Тело запроса не прошло валидацию (пустой массив или некорректный UUID). |
| 401 | unauthorized | Токен отсутствует, истёк или недействителен. |
| 403 | forbidden | Нет права manage_courses. |
| 409 | reorder_duplicate | В ordered_ids есть повторяющиеся идентификаторы. |
| 409 | reorder_incomplete | Список не совпадает с полным набором блоков урока. |