Группы
Когорты студентов: CRUD групп, назначение куратора, управление составом, обратный поиск групп студента и постоянные групповые зачисления на курсы. Все эндпоинты живут под /api/v1/t/* и требуют JWT + заголовок X-Tenant-Subdomain.
- GET
/api/v1/t/groupsСписок групп - GET
/api/v1/t/groups/:idПолучить группу - POST
/api/v1/t/groupsСоздать группу - PATCH
/api/v1/t/groups/:idИзменить группу - PUT
/api/v1/t/groups/:id/curatorНазначить куратора группы - DELETE
/api/v1/t/groups/:idУдалить группу - GET
/api/v1/t/groups/:id/membersСписок участников группы - POST
/api/v1/t/groups/:id/membersДобавить участников - DELETE
/api/v1/t/groups/:id/membersУдалить участников - GET
/api/v1/t/users/:id/groupsГруппы студента - GET
/api/v1/t/courses/:id/group-enrollmentsЗачисленные группы курса - POST
/api/v1/t/courses/:id/group-enrollmentsЗачислить группу на курс - DELETE
/api/v1/t/courses/:id/group-enrollments/:group_idСнять группу с курса - POST
/api/v1/t/courses/:id/enroll-bulkЗачислить группу или список студентов
/api/v1/t/groupsСписок групп
Постраничный список групп школы с количеством участников и гидрированным куратором. Непривилегированный куратор видит только свои группы (curator_id = он сам); назначенные другим или без куратора группы скрыты.
Конверт: { groups, total, limit, offset }. limit 1..200 (по умолчанию 50), offset >= 0. Поле logo — сырое значение (UUID медиа-ассета или URL), для формы редактирования. Поле logo_url — готовая ссылка (presigned URL, если logo — UUID), для отображения. Оба null, если логотипа нет.
Query-параметры
| Параметр | Тип | Описание |
|---|---|---|
qопц. | string | Поиск по названию группы. |
limitопц. | integer | Размер страницы (1..200). По умолчанию 50.Пример: 50 |
offsetопц. | integer | Смещение (>=0). По умолчанию 0.Пример: 0 |
Пример запроса
curl "https://api.edumentor.kz/api/v1/t/groups?limit=50&offset=0" \
-H "Authorization: Bearer <token>" \
-H "X-Tenant-Subdomain: acme"Пример ответа
{
"success": true,
"data": {
"groups": [
{
"id": "g1g1...",
"name": "Поток 2026",
"description": "Весенний набор",
"logo": "a1b2c3d4-0000-4000-8000-000000000000",
"logo_url": "https://minio.edumentor.kz/lms/.../logo.png?X-Amz-Signature=...",
"created_by": "c1c1...",
"curator_id": "c2c2...",
"curator_first_name": "Дана",
"curator_last_name": "Серик",
"curator_email": "curator@acme.kz",
"member_count": 0,
"created_at": "2026-05-28T09:00:00Z",
"updated_at": "2026-05-28T09:00:00Z"
}
],
"total": 1,
"limit": 50,
"offset": 0
}
}Ошибки
| Код | error | Когда |
|---|---|---|
| 400 | invalid_params | Некорректные limit/offset (вне диапазона). |
| 401 | unauthorized | Школа не определена или токен недействителен. |
/api/v1/t/groups/:idПолучить группу
Возвращает одну группу с количеством участников и гидрированным куратором. Кросс-тенантный ID отдаётся как 404. Для непривилегированного куратора группа вне его охвата или без куратора схлопывается в 404.
Параметры пути
| Параметр | Тип | Описание |
|---|---|---|
idобяз. | uuid | ID группы. |
Пример запроса
curl https://api.edumentor.kz/api/v1/t/groups/g1g1... \
-H "Authorization: Bearer <token>" \
-H "X-Tenant-Subdomain: acme"Пример ответа
{
"success": true,
"data": {
"id": "g1g1...",
"name": "Поток 2026",
"description": "Весенний набор",
"logo": "a1b2c3d4-0000-4000-8000-000000000000",
"logo_url": "https://minio.edumentor.kz/lms/.../logo.png?X-Amz-Signature=...",
"created_by": "c1c1...",
"curator_id": "c2c2...",
"curator_first_name": "Дана",
"curator_last_name": "Серик",
"curator_email": "curator@acme.kz",
"member_count": 12,
"created_at": "2026-05-28T09:00:00Z",
"updated_at": "2026-05-28T09:00:00Z"
}
}Ошибки
| Код | error | Когда |
|---|---|---|
| 400 | invalid_id_format | ID в пути не является UUID. |
| 401 | unauthorized | Школа не определена или токен недействителен. |
| 404 | group_not_found | Группа отсутствует, другая школа или вне охвата куратора. |
/api/v1/t/groupsСоздать группу
Создаёт новую группу (когорту). 201 Created. Создатель берётся из JWT.
Тело запроса
| Параметр | Тип | Описание |
|---|---|---|
nameобяз. | string | Название группы (1..120). |
descriptionопц. | string | Описание (до 2000 символов). |
logoопц. | string | Логотип: UUID подтверждённого медиа-ассета (kind=image) ИЛИ URL картинки. Опустите — без логотипа. |
Пример запроса
curl -X POST https://api.edumentor.kz/api/v1/t/groups \
-H "Authorization: Bearer <token>" \
-H "X-Tenant-Subdomain: acme" \
-H "Content-Type: application/json" \
-d '{ "name": "Поток 2026", "description": "Весенний набор" }'Пример ответа
{
"success": true,
"data": {
"id": "g1g1...",
"name": "Поток 2026",
"description": "Весенний набор",
"logo": null,
"logo_url": null,
"created_by": "c1c1...",
"curator_id": null,
"member_count": 0,
"created_at": "2026-05-28T09:00:00Z",
"updated_at": "2026-05-28T09:00:00Z"
}
}Ошибки
| Код | error | Когда |
|---|---|---|
| 400 | invalid_request_body | Некорректное тело запроса. |
| 400 | group_name_required | Название группы обязательно. |
| 401 | unauthorized | Школа не определена или токен недействителен. |
| 403 | permission_denied | Нет права manage_groups. |
/api/v1/t/groups/:idИзменить группу
Частичное обновление названия/описания/логотипа группы. Переданные поля заменяют значения; опущенные — не трогаются.
Параметры пути
| Параметр | Тип | Описание |
|---|---|---|
idобяз. | uuid | ID группы. |
Тело запроса
| Параметр | Тип | Описание |
|---|---|---|
nameопц. | string | Новое название (1..120). |
descriptionопц. | string | Новое описание (до 2000). |
logoопц. | string | Логотип: UUID медиа-ассета или URL картинки. Пустая строка "" — снять логотип (NULL). Опустите — оставить как есть. |
Пример запроса
curl -X PATCH https://api.edumentor.kz/api/v1/t/groups/g1g1... \
-H "Authorization: Bearer <token>" \
-H "X-Tenant-Subdomain: acme" \
-H "Content-Type: application/json" \
-d '{ "name": "Поток 2026 (обновлён)" }'Пример ответа
{
"success": true,
"data": {
"id": "g1g1...",
"name": "Поток 2026 (обновлён)",
"description": "Весенний набор",
"logo": "a1b2c3d4-0000-4000-8000-000000000000",
"logo_url": "https://minio.edumentor.kz/lms/.../logo.png?X-Amz-Signature=...",
"created_by": "c1c1...",
"curator_id": "c2c2...",
"member_count": 12,
"created_at": "2026-05-28T09:00:00Z",
"updated_at": "2026-05-28T12:00:00Z"
}
}Ошибки
| Код | error | Когда |
|---|---|---|
| 400 | invalid_id_format | ID в пути не является UUID. |
| 400 | invalid_request_body | Некорректное тело запроса. |
| 400 | group_name_required | Название группы не может быть пустым. |
| 401 | unauthorized | Школа не определена или токен недействителен. |
| 403 | permission_denied | Нет права manage_groups. |
| 404 | group_not_found | Группа отсутствует или принадлежит другой школе. |
/api/v1/t/groups/:id/curatorНазначить куратора группы
Назначает (или снимает) куратора группы. curator_id = null означает «снять куратора». Целевой пользователь проверяется на роль и принадлежность школе.
Тело запроса
| Параметр | Тип | Описание |
|---|---|---|
curator_idопц. | uuid | ID куратора. null — снять назначение. |
Пример запроса
curl -X PUT https://api.edumentor.kz/api/v1/t/groups/g1g1.../curator \
-H "Authorization: Bearer <token>" \
-H "X-Tenant-Subdomain: acme" \
-H "Content-Type: application/json" \
-d '{ "curator_id": "c2c2..." }'Пример ответа
{
"success": true,
"data": {
"id": "g1g1...",
"name": "Поток 2026",
"description": "Весенний набор",
"logo": "a1b2c3d4-0000-4000-8000-000000000000",
"logo_url": "https://minio.edumentor.kz/lms/.../logo.png?X-Amz-Signature=...",
"created_by": "c1c1...",
"curator_id": "c2c2...",
"curator_first_name": "Дана",
"curator_last_name": "Серик",
"curator_email": "curator@acme.kz",
"member_count": 12,
"created_at": "2026-05-28T09:00:00Z",
"updated_at": "2026-05-28T12:00:00Z"
}
}Ошибки
| Код | error | Когда |
|---|---|---|
| 400 | invalid_id_format | ID в пути не является UUID. |
| 400 | invalid_request_body | Некорректное тело запроса. |
| 400 | curator_not_eligible | Целевой пользователь не подходит на роль куратора (роль/школа). |
| 401 | unauthorized | Школа не определена или токен недействителен. |
| 403 | permission_denied | Нет права manage_groups. |
| 404 | group_not_found | Группа отсутствует или принадлежит другой школе. |
/api/v1/t/groups/:idУдалить группу
Мягко удаляет группу вместе с её членствами (каскад внутри транзакции). Группу с активными зачислениями на курсы сначала нужно снять с этих курсов. Возвращает { deleted: true }.
Параметры пути
| Параметр | Тип | Описание |
|---|---|---|
idобяз. | uuid | ID группы. |
Пример запроса
curl -X DELETE https://api.edumentor.kz/api/v1/t/groups/g1g1... \
-H "Authorization: Bearer <token>" \
-H "X-Tenant-Subdomain: acme"Пример ответа
{
"success": true,
"data": { "deleted": true }
}Ошибки
| Код | error | Когда |
|---|---|---|
| 400 | invalid_id_format | ID в пути не является UUID. |
| 401 | unauthorized | Школа не определена или токен недействителен. |
| 403 | permission_denied | Нет права manage_groups. |
| 404 | group_not_found | Группа отсутствует или принадлежит другой школе. |
| 409 | group_has_course_enrollments | Группа ещё зачислена хотя бы на один курс. |
/api/v1/t/groups/:id/membersСписок участников группы
Постраничный список участников группы с email и именем студента. Конверт: { members, total, limit, offset }. Для непривилегированного куратора группа вне охвата схлопывается в 404.
limit 1..200 (по умолчанию 50), offset >= 0.
Параметры пути
| Параметр | Тип | Описание |
|---|---|---|
idобяз. | uuid | ID группы. |
Query-параметры
| Параметр | Тип | Описание |
|---|---|---|
limitопц. | integer | Размер страницы (1..200). По умолчанию 50.Пример: 50 |
offsetопц. | integer | Смещение (>=0). По умолчанию 0.Пример: 0 |
Пример запроса
curl "https://api.edumentor.kz/api/v1/t/groups/g1g1.../members?limit=50&offset=0" \
-H "Authorization: Bearer <token>" \
-H "X-Tenant-Subdomain: acme"Пример ответа
{
"success": true,
"data": {
"members": [
{
"membership_id": "mm1...",
"student_id": "8f3b...",
"email": "student@acme.kz",
"first_name": "Айгерим",
"last_name": "Нурлан",
"is_active": true,
"joined_at": "2026-05-28T09:30:00Z"
}
],
"total": 1,
"limit": 50,
"offset": 0
}
}Ошибки
| Код | error | Когда |
|---|---|---|
| 400 | invalid_id_format | ID в пути не является UUID. |
| 400 | invalid_params | Некорректные limit/offset. |
| 401 | unauthorized | Школа не определена или токен недействителен. |
| 404 | group_not_found | Группа отсутствует, другая школа или вне охвата куратора. |
/api/v1/t/groups/:id/membersДобавить участников
Добавляет список студентов в группу (частичный успех). Если группа зачислена на курсы, новым участникам в той же операции выдаются групповые источники доступа. Каждый необработанный студент попадает в skipped[] с причиной: already_member | user_not_found | wrong_tenant.
Параметры пути
| Параметр | Тип | Описание |
|---|---|---|
idобяз. | uuid | ID группы. |
Тело запроса
| Параметр | Тип | Описание |
|---|---|---|
student_idsобяз. | string[] | Массив UUID студентов (от 1 до 500). |
Пример запроса
curl -X POST https://api.edumentor.kz/api/v1/t/groups/g1g1.../members \
-H "Authorization: Bearer <token>" \
-H "X-Tenant-Subdomain: acme" \
-H "Content-Type: application/json" \
-d '{ "student_ids": ["8f3b...", "7a2c..."] }'Пример ответа
{
"success": true,
"data": {
"added": ["8f3b..."],
"skipped": [{ "student_id": "7a2c...", "reason": "already_member" }]
}
}Ошибки
| Код | error | Когда |
|---|---|---|
| 400 | invalid_id_format | ID группы в пути не является UUID. |
| 400 | invalid_request_body | Некорректное тело запроса. |
| 400 | empty_member_list | Список студентов пуст. |
| 400 | too_many_members | Превышен лимит количества студентов в одном запросе. |
| 400 | cross_tenant_student | Студент принадлежит другой школе. |
| 401 | unauthorized | Школа не определена или токен недействителен. |
| 403 | permission_denied | Нет права manage_groups. |
| 404 | group_not_found | Группа отсутствует или принадлежит другой школе. |
/api/v1/t/groups/:id/membersУдалить участников
Массово удаляет студентов из группы. На назначенных группе курсах снимается только источник этой группы; доступ через другие группы или прямое зачисление сохраняется, учебный прогресс не удаляется. student_ids передаются в теле запроса. Возвращает { removed: true }.
Параметры пути
| Параметр | Тип | Описание |
|---|---|---|
idобяз. | uuid | ID группы. |
Тело запроса
| Параметр | Тип | Описание |
|---|---|---|
student_idsобяз. | string[] | Массив UUID студентов для удаления (от 1 до 500). |
Пример запроса
curl -X DELETE https://api.edumentor.kz/api/v1/t/groups/g1g1.../members \
-H "Authorization: Bearer <token>" \
-H "X-Tenant-Subdomain: acme" \
-H "Content-Type: application/json" \
-d '{ "student_ids": ["8f3b..."] }'Пример ответа
{
"success": true,
"data": { "removed": true }
}Ошибки
| Код | error | Когда |
|---|---|---|
| 400 | invalid_id_format | ID группы в пути не является UUID. |
| 400 | invalid_request_body | Некорректное тело запроса. |
| 400 | empty_member_list | Список студентов пуст. |
| 400 | too_many_members | Превышен лимит количества студентов в одном запросе. |
| 401 | unauthorized | Школа не определена или токен недействителен. |
| 403 | permission_denied | Нет права manage_groups. |
| 404 | group_not_found | Группа отсутствует или принадлежит другой школе. |
| 404 | member_not_found | Один из переданных студентов не является участником группы. |
/api/v1/t/users/:id/groupsГруппы студента
Обратный поиск: в каких группах состоит студент. Кросс-тенантный student_id даёт пустой список (приватность по паритету). Непривилегированный куратор видит только пересечение со своими группами.
Параметры пути
| Параметр | Тип | Описание |
|---|---|---|
idобяз. | uuid | ID студента. |
Пример запроса
curl https://api.edumentor.kz/api/v1/t/users/8f3b.../groups \
-H "Authorization: Bearer <token>" \
-H "X-Tenant-Subdomain: acme"Пример ответа
{
"success": true,
"data": {
"groups": [
{
"group_id": "g1g1...",
"name": "Поток 2026",
"description": "Весенний набор",
"joined_at": "2026-05-28T09:30:00Z"
}
]
}
}Ошибки
| Код | error | Когда |
|---|---|---|
| 400 | invalid_id_format | ID студента в пути не является UUID. |
| 401 | unauthorized | Школа не определена или токен недействителен. |
| 403 | permission_denied | Нет права manage_students. |
/api/v1/t/courses/:id/group-enrollmentsЗачисленные группы курса
Возвращает постоянные связи курса с группами. Состав синхронизируется автоматически: новый участник получает групповой источник доступа, удалённый участник теряет только источник этой группы.
counts.groups — общее число связей, counts.students — число уникальных учеников с групповым источником доступа. would_remove_count и would_retain_count показывают текущий эффект снятия конкретной группы. Поиск q фильтрует по названию или описанию. Некорректная page заменяется на 1, а limit вне 1..100 — на 20.
Параметры пути
| Параметр | Тип | Описание |
|---|---|---|
idобяз. | uuid | ID курса. |
Query-параметры
| Параметр | Тип | Описание |
|---|---|---|
qопц. | string | Поиск по названию или описанию группы. |
pageопц. | integer | Номер страницы (с 1).Пример: 1 |
limitопц. | integer | Размер страницы (1..100).Пример: 20 |
Пример запроса
curl "https://api.edumentor.kz/api/v1/t/courses/2b1a.../group-enrollments?page=1&limit=20" -H "Authorization: Bearer <token>" -H "X-Tenant-Subdomain: acme"Пример ответа
{
"success": true,
"data": {
"group_enrollments": [
{
"id": "cge1...",
"course_id": "2b1a...",
"group_id": "g1g1...",
"name": "Поток 2026",
"description": "Весенний набор",
"logo": null,
"logo_url": null,
"member_count": 24,
"enrolled_students_count": 24,
"would_remove_count": 18,
"would_retain_count": 6,
"enrolled_at": "2026-08-03T10:00:00Z",
"enrolled_by": "c1c1..."
}
],
"counts": { "groups": 1, "students": 24 },
"total": 1,
"page": 1,
"limit": 20
}
}Ошибки
| Код | error | Когда |
|---|---|---|
| 400 | invalid_course_id | ID курса в пути не является UUID. |
| 401 | unauthorized | Школа не определена или токен недействителен. |
| 403 | permission_denied | Нет права manage_groups. |
| 404 | course_not_found | Курс отсутствует или принадлежит другой школе. |
/api/v1/t/courses/:id/group-enrollmentsЗачислить группу на курс
Создаёт постоянную связь группы с курсом и синхронизирует групповые источники доступа для текущих участников. Идемпотентно: повторный запрос сверяет источники и возвращает существующую связь.
201 Created — новая связь; 200 OK — связь уже существовала. enrolled_count показывает реальные переходы к доступу, already_enrolled_count — участников, у которых доступ уже был. В group_enrollment возвращается текущий прогноз снятия связи: would_remove_count и would_retain_count.
Параметры пути
| Параметр | Тип | Описание |
|---|---|---|
idобяз. | uuid | ID курса. |
Тело запроса
| Параметр | Тип | Описание |
|---|---|---|
group_idобяз. | uuid | ID группы. |
Пример запроса
curl -X POST https://api.edumentor.kz/api/v1/t/courses/2b1a.../group-enrollments -H "Authorization: Bearer <token>" -H "X-Tenant-Subdomain: acme" -H "Content-Type: application/json" -d '{ "group_id": "g1g1..." }'Пример ответа
{
"success": true,
"data": {
"group_enrollment": {
"id": "cge1...",
"course_id": "2b1a...",
"group_id": "g1g1...",
"name": "Поток 2026",
"description": "Весенний набор",
"logo": null,
"logo_url": null,
"member_count": 24,
"enrolled_students_count": 24,
"would_remove_count": 20,
"would_retain_count": 4,
"enrolled_at": "2026-08-03T10:00:00Z",
"enrolled_by": "c1c1..."
},
"enrolled_count": 20,
"already_enrolled_count": 4
}
}Ошибки
| Код | error | Когда |
|---|---|---|
| 400 | invalid_course_id | ID курса в пути не является UUID. |
| 400 | invalid_request_body | Некорректное тело запроса. |
| 401 | unauthorized | Школа не определена или токен недействителен. |
| 403 | permission_denied | Нет права manage_groups. |
| 404 | course_not_found | Курс отсутствует, не опубликован или принадлежит другой школе. |
| 404 | group_not_found | Группа отсутствует или принадлежит другой школе. |
/api/v1/t/courses/:id/group-enrollments/:group_idСнять группу с курса
Удаляет связь и источники этой группы. Ученик остаётся на курсе, если у него есть прямой, legacy или другой групповой источник; прогресс и сертификаты сохраняются.
Параметры пути
| Параметр | Тип | Описание |
|---|---|---|
idобяз. | uuid | ID курса. |
group_idобяз. | uuid | ID группы. |
Пример запроса
curl -X DELETE https://api.edumentor.kz/api/v1/t/courses/2b1a.../group-enrollments/g1g1... -H "Authorization: Bearer <token>" -H "X-Tenant-Subdomain: acme"Пример ответа
{
"success": true,
"data": { "removed_count": 18, "retained_count": 6 }
}Ошибки
| Код | error | Когда |
|---|---|---|
| 400 | invalid_course_id | ID курса в пути не является UUID. |
| 400 | invalid_group_id | ID группы в пути не является UUID. |
| 401 | unauthorized | Школа не определена или токен недействителен. |
| 403 | permission_denied | Нет права manage_groups. |
| 404 | course_group_enrollment_not_found | Активная связь курса с группой не найдена. |
/api/v1/t/courses/:id/enroll-bulkЗачислить группу или список студентов
Принимает ровно одно из полей. group_id работает как совместимый фасад: создаёт постоянную связь и включает автосинхронизацию состава, как POST /courses/:id/group-enrollments. student_ids выполняет разовое прямое зачисление.
Отличается от POST /api/v1/t/courses/:id/enroll/bulk (через слэш) из раздела «Записи на курсы»: другой URL (дефис) и право manage_groups вместо manage_students. Для group_id ответ сохраняет legacy-форму enrolled/skipped/total, но связь является постоянной. Для student_ids всегда 200, даже при частичном успехе. Total = enrolled + skipped.
Параметры пути
| Параметр | Тип | Описание |
|---|---|---|
idобяз. | uuid | ID курса. |
Тело запроса
| Параметр | Тип | Описание |
|---|---|---|
group_idопц. | uuid | ID группы для постоянного группового зачисления. Взаимоисключающе с student_ids. |
student_idsопц. | string[] | Явный список UUID студентов (1..500). Взаимоисключающе с group_id. |
Пример запроса
curl -X POST https://api.edumentor.kz/api/v1/t/courses/2b1a.../enroll-bulk \
-H "Authorization: Bearer <token>" \
-H "X-Tenant-Subdomain: acme" \
-H "Content-Type: application/json" \
-d '{ "group_id": "g1g1..." }'Пример ответа
{
"success": true,
"data": {
"enrolled": ["8f3b...", "7a2c..."],
"skipped": [],
"total": 2
}
}Ошибки
| Код | error | Когда |
|---|---|---|
| 400 | invalid_course_id | ID курса в пути не является UUID. |
| 400 | invalid_request_body | Некорректное тело запроса. |
| 400 | invalid_bulk_enroll_payload | Нарушено правило «ровно одно из group_id / student_ids». |
| 400 | too_many_members | Превышен лимит количества студентов. |
| 401 | unauthorized | Школа не определена или токен недействителен. |
| 403 | permission_denied | Нет права manage_groups. |
| 404 | course_not_found | Курс отсутствует, в черновике или принадлежит другой школе. |
| 404 | group_not_found | Группа отсутствует или принадлежит другой школе. |