TenantAdminCuratorStudent

Пользователи

Управление пользователями школы (ученики, кураторы, администраторы): создание, список, роли, права кураторов, массовые операции и самообслуживание профиля `/me`. Все эндпоинты тенант-скоупные и требуют заголовка субдомена.

POST/api/v1/t/users

Создать пользователя

AdminCurator JWT право: manage_students

Создаёт пользователя в текущей школе. Доступно админу и кураторам с правом manage_students. Email приводится к нижнему регистру и должен быть уникален в рамках школы. Для роли curator автоматически создаётся набор прав по умолчанию.

Тело запроса

ПараметрТипОписание
emailобяз.stringEmail пользователя (валидный email).
passwordобяз.stringПароль (минимум 8 символов).
first_nameобяз.stringИмя.
last_nameобяз.stringФамилия.
roleобяз.stringРоль: admin, curator или student.Пример: student

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

cURL
curl -X POST https://api.edumentor.kz/api/v1/t/users \
  -H "Authorization: Bearer <token>" \
  -H "X-Tenant-Subdomain: acme" \
  -H "Content-Type: application/json" \
  -d '{
    "email": "student@acme.kz",
    "password": "super-secret",
    "first_name": "Дамир",
    "last_name": "Сериков",
    "role": "student"
  }'

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

200 OK · application/json
{
  "success": true,
  "data": {
    "id": "8f3b1c2a-1d2e-4a5b-9c6d-7e8f9a0b1c2d",
    "tenant_id": "1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
    "email": "student@acme.kz",
    "first_name": "Дамир",
    "last_name": "Сериков",
    "avatar": null,
    "role": "student",
    "is_active": true,
    "last_active_at": null,
    "created_at": "2026-05-28T10:00:00Z",
    "updated_at": "2026-05-28T10:00:00Z"
  }
}

Ошибки

КодerrorКогда
400email already takenПользователь с таким email уже существует в этой школе.
401unauthorizedТокен отсутствует, истёк или недействителен.
403forbiddenНет права manage_students.
GET/api/v1/t/users

Список пользователей

AdminCurator JWT право: manage_students

Постранично возвращает пользователей школы с фильтрами по роли, статусу и поисковой строке. Доступно админу и кураторам с правом manage_students. Непривилегированные кураторы видят только своих учеников (my_students).

Параметр role принимает одно значение (?role=student) или несколько (?role=curator,admin либо повтор ?role=curator&role=admin). Для списка учеников каждая строка дополняется полем group_count и массивом groups (брифы групп: group_id, name, logo), упорядоченным по дате вступления.

Query-параметры

ПараметрТипОписание
pageопц.integerНомер страницы (>= 1). По умолчанию 1.
limitопц.integerРазмер страницы (1–200). По умолчанию 20.
roleопц.stringФильтр по роли — одно или несколько значений.Пример: student
statusопц.stringФильтр по статусу: active или inactive.Пример: active
qопц.stringПоиск по имени/email.

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

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

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

200 OK · application/json
{
  "success": true,
  "data": {
    "users": [
      {
        "id": "8f3b1c2a-1d2e-4a5b-9c6d-7e8f9a0b1c2d",
        "tenant_id": "1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
        "email": "student@acme.kz",
        "first_name": "Дамир",
        "last_name": "Сериков",
        "avatar": null,
        "role": "student",
        "is_active": true,
        "last_active_at": "2026-05-28T09:50:00Z",
        "group_count": 2,
        "groups": [
          { "group_id": "g1g1...", "name": "Поток 2026", "logo": "a1b2c3d4-0000-4000-8000-000000000000" },
          { "group_id": "g2g2...", "name": "Группа B", "logo": null }
        ],
        "created_at": "2026-05-20T10:00:00Z",
        "updated_at": "2026-05-28T09:50:00Z"
      }
    ],
    "total": 1,
    "page": 1,
    "limit": 20
  }
}

Ошибки

КодerrorКогда
400invalid_paramsНекорректные page/limit (page < 1 или limit вне диапазона 1–200).
401unauthorizedТокен отсутствует, истёк или недействителен.
403forbiddenНет права manage_students.
POST/api/v1/t/users/bulk-activate

Массовая активация

AdminCurator JWT право: manage_students

Включает (is_active=true) сразу несколько пользователей по списку идентификаторов. Идемпотентно: уже активные пропускаются. Доступно админу и кураторам с правом manage_students.

Если в ids присутствует собственный идентификатор вызывающего — отклоняется ВЕСЬ запрос (400 cannot_modify_self). Для непривилегированного куратора чужие ученики попадают в skipped_details с reason not_in_scope.

Тело запроса

ПараметрТипОписание
idsобяз.string[]Список UUID пользователей (1–100).

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

cURL
curl -X POST https://api.edumentor.kz/api/v1/t/users/bulk-activate \
  -H "Authorization: Bearer <token>" \
  -H "X-Tenant-Subdomain: acme" \
  -H "Content-Type: application/json" \
  -d '{ "ids": ["8f3b1c2a-1d2e-4a5b-9c6d-7e8f9a0b1c2d"] }'

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

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

Ошибки

КодerrorКогда
400invalid_paramsТело запроса не прошло валидацию (пустой список или > 100 идентификаторов).
400cannot_modify_selfВ списке присутствует собственный идентификатор вызывающего.
401unauthorizedТокен отсутствует, истёк или недействителен.
403forbiddenНет права manage_students.
POST/api/v1/t/users/bulk-deactivate

Массовая деактивация

AdminCurator JWT право: manage_students

Выключает (is_active=false) сразу несколько пользователей по списку идентификаторов. Симметрично массовой активации: идемпотентно, тенант-скоупно, с защитой от самодеактивации.

Если в ids присутствует собственный идентификатор вызывающего — отклоняется ВЕСЬ запрос (400 cannot_modify_self).

Тело запроса

ПараметрТипОписание
idsобяз.string[]Список UUID пользователей (1–100).

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

cURL
curl -X POST https://api.edumentor.kz/api/v1/t/users/bulk-deactivate \
  -H "Authorization: Bearer <token>" \
  -H "X-Tenant-Subdomain: acme" \
  -H "Content-Type: application/json" \
  -d '{ "ids": ["8f3b1c2a-1d2e-4a5b-9c6d-7e8f9a0b1c2d"] }'

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

200 OK · application/json
{
  "success": true,
  "data": {
    "affected": 1,
    "skipped": 0,
    "skipped_details": [
      { "id": "2b3c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e", "reason": "not_in_scope" }
    ]
  }
}

Ошибки

КодerrorКогда
400invalid_paramsТело запроса не прошло валидацию (пустой список или > 100 идентификаторов).
400cannot_modify_selfВ списке присутствует собственный идентификатор вызывающего.
401unauthorizedТокен отсутствует, истёк или недействителен.
403forbiddenНет права manage_students.
GET/api/v1/t/users/:id

Пользователь по ID

AdminCurator JWT право: manage_students

Возвращает одного пользователя школы (с правами куратора, если применимо). Доступно админу и кураторам с правом manage_students. Для непривилегированного куратора ученик вне его my_students возвращает 404 — тот же отпечаток, что и отсутствующий.

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

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

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

cURL
curl https://api.edumentor.kz/api/v1/t/users/8f3b1c2a-1d2e-4a5b-9c6d-7e8f9a0b1c2d \
  -H "Authorization: Bearer <token>" \
  -H "X-Tenant-Subdomain: acme"

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

200 OK · application/json
{
  "success": true,
  "data": {
    "id": "8f3b1c2a-1d2e-4a5b-9c6d-7e8f9a0b1c2d",
    "tenant_id": "1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
    "email": "student@acme.kz",
    "first_name": "Дамир",
    "last_name": "Сериков",
    "avatar": null,
    "role": "student",
    "is_active": true,
    "last_active_at": "2026-05-28T09:50:00Z",
    "group_count": 2,
    "groups": [
      { "group_id": "g1g1...", "name": "Поток 2026", "logo": "a1b2c3d4-0000-4000-8000-000000000000" },
      { "group_id": "g2g2...", "name": "Группа B", "logo": null }
    ],
    "created_at": "2026-05-20T10:00:00Z",
    "updated_at": "2026-05-28T09:50:00Z"
  }
}

Ошибки

КодerrorКогда
400invalid id formatИдентификатор не является корректным UUID.
401unauthorizedТокен отсутствует, истёк или недействителен.
403forbiddenНет права manage_students.
404user_not_foundПользователь не найден либо вне области видимости куратора.
PUT/api/v1/t/users/:id

Обновить пользователя

Admin JWT

Частично обновляет профиль пользователя: имя, фамилию, аватар, флаг активности. Доступно только роли admin.

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

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

Тело запроса

ПараметрТипОписание
first_nameопц.stringИмя (минимум 2 символа).
last_nameопц.stringФамилия (минимум 2 символа).
avatarопц.stringURL аватара или UUID медиа-ассета (изображение). Для UUID на чтение возвращается готовый presigned-URL.
is_activeопц.booleanАктивность учётной записи.

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

cURL
curl -X PUT https://api.edumentor.kz/api/v1/t/users/8f3b1c2a-1d2e-4a5b-9c6d-7e8f9a0b1c2d \
  -H "Authorization: Bearer <token>" \
  -H "X-Tenant-Subdomain: acme" \
  -H "Content-Type: application/json" \
  -d '{ "first_name": "Дамир", "is_active": false }'

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

200 OK · application/json
{
  "success": true,
  "data": {
    "id": "8f3b1c2a-1d2e-4a5b-9c6d-7e8f9a0b1c2d",
    "tenant_id": "1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
    "email": "student@acme.kz",
    "first_name": "Дамир",
    "last_name": "Сериков",
    "avatar": null,
    "role": "student",
    "is_active": false,
    "created_at": "2026-05-20T10:00:00Z",
    "updated_at": "2026-05-28T12:00:00Z"
  }
}

Ошибки

КодerrorКогда
400invalid id formatИдентификатор не является корректным UUID.
401unauthorizedТокен отсутствует, истёк или недействителен.
403forbiddenНедостаточно прав — требуется роль администратора.
404user not foundПользователь с таким идентификатором не найден в школе.
DELETE/api/v1/t/users/:id

Удалить пользователя

Admin JWT

Мягко удаляет пользователя школы (проставляет deleted_at). Доступно только роли admin. Нельзя удалить самого себя.

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

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

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

cURL
curl -X DELETE https://api.edumentor.kz/api/v1/t/users/8f3b1c2a-1d2e-4a5b-9c6d-7e8f9a0b1c2d \
  -H "Authorization: Bearer <token>" \
  -H "X-Tenant-Subdomain: acme"

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

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

Ошибки

КодerrorКогда
400invalid id formatИдентификатор не является корректным UUID.
400cannot delete yourselfПопытка удалить собственную учётную запись.
401unauthorizedТокен отсутствует, истёк или недействителен.
403forbiddenНедостаточно прав — требуется роль администратора.
PUT/api/v1/t/users/:id/role

Изменить роль

Admin JWT

Меняет роль пользователя на admin, curator или student. Доступно только роли admin. При назначении роли curator автоматически создаётся набор прав по умолчанию, если его ещё нет.

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

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

Тело запроса

ПараметрТипОписание
roleобяз.stringНовая роль: admin, curator или student.Пример: curator

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

cURL
curl -X PUT https://api.edumentor.kz/api/v1/t/users/8f3b1c2a-1d2e-4a5b-9c6d-7e8f9a0b1c2d/role \
  -H "Authorization: Bearer <token>" \
  -H "X-Tenant-Subdomain: acme" \
  -H "Content-Type: application/json" \
  -d '{ "role": "curator" }'

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

200 OK · application/json
{
  "success": true,
  "data": {
    "id": "8f3b1c2a-1d2e-4a5b-9c6d-7e8f9a0b1c2d",
    "tenant_id": "1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
    "email": "student@acme.kz",
    "first_name": "Дамир",
    "last_name": "Сериков",
    "role": "curator",
    "is_active": true,
    "permissions": {
      "id": "9c0d1e2f-3a4b-5c6d-7e8f-9a0b1c2d3e4f",
      "user_id": "8f3b1c2a-1d2e-4a5b-9c6d-7e8f9a0b1c2d",
      "can_manage_courses": true,
      "can_manage_students": true,
      "can_manage_groups": true,
      "can_view_analytics": false,
      "can_manage_exams": false,
      "created_at": "2026-05-28T12:30:00Z",
      "updated_at": "2026-05-28T12:30:00Z"
    },
    "created_at": "2026-05-20T10:00:00Z",
    "updated_at": "2026-05-28T12:30:00Z"
  }
}

Ошибки

КодerrorКогда
400invalid id formatИдентификатор не является корректным UUID.
400validation errorПоле role отсутствует или не входит в {admin, curator, student}.
401unauthorizedТокен отсутствует, истёк или недействителен.
403forbiddenНедостаточно прав — требуется роль администратора.
404user not foundПользователь с таким идентификатором не найден в школе.
GET/api/v1/t/users/:id/permissions

Права куратора

Admin JWT

Возвращает набор детальных прав куратора. Доступно только роли admin. Целевой пользователь должен иметь роль curator.

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

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

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

cURL
curl https://api.edumentor.kz/api/v1/t/users/8f3b1c2a-1d2e-4a5b-9c6d-7e8f9a0b1c2d/permissions \
  -H "Authorization: Bearer <token>" \
  -H "X-Tenant-Subdomain: acme"

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

200 OK · application/json
{
  "success": true,
  "data": {
    "id": "9c0d1e2f-3a4b-5c6d-7e8f-9a0b1c2d3e4f",
    "user_id": "8f3b1c2a-1d2e-4a5b-9c6d-7e8f9a0b1c2d",
    "can_manage_courses": true,
    "can_manage_students": true,
    "can_manage_groups": true,
    "can_view_analytics": false,
    "can_manage_exams": false,
    "created_at": "2026-05-28T12:30:00Z",
    "updated_at": "2026-05-28T12:30:00Z"
  }
}

Ошибки

КодerrorКогда
400invalid id formatИдентификатор не является корректным UUID.
400user is not a curatorУ целевого пользователя роль не curator.
400permissions not foundДля куратора ещё не создан набор прав.
401unauthorizedТокен отсутствует, истёк или недействителен.
403forbiddenНедостаточно прав — требуется роль администратора.
PUT/api/v1/t/users/:id/permissions

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

Admin JWT

Перезаписывает набор детальных прав куратора. Доступно только роли admin. Целевой пользователь должен иметь роль curator. Эти флаги управляют гейтами manage_courses, manage_students, can_manage_groups, manage_exams, view_analytics.

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

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

Тело запроса

ПараметрТипОписание
can_manage_coursesобяз.booleanПраво управления курсами.
can_manage_studentsобяз.booleanПраво управления учениками.
can_manage_groupsобяз.booleanПраво управления группами.
can_view_analyticsобяз.booleanПраво просмотра аналитики.
can_manage_examsобяз.booleanПраво управления экзаменами.

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

cURL
curl -X PUT https://api.edumentor.kz/api/v1/t/users/8f3b1c2a-1d2e-4a5b-9c6d-7e8f9a0b1c2d/permissions \
  -H "Authorization: Bearer <token>" \
  -H "X-Tenant-Subdomain: acme" \
  -H "Content-Type: application/json" \
  -d '{
    "can_manage_courses": true,
    "can_manage_students": true,
    "can_manage_groups": false,
    "can_view_analytics": true,
    "can_manage_exams": false
  }'

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

200 OK · application/json
{
  "success": true,
  "data": {
    "id": "9c0d1e2f-3a4b-5c6d-7e8f-9a0b1c2d3e4f",
    "user_id": "8f3b1c2a-1d2e-4a5b-9c6d-7e8f9a0b1c2d",
    "can_manage_courses": true,
    "can_manage_students": true,
    "can_manage_groups": false,
    "can_view_analytics": true,
    "can_manage_exams": false,
    "created_at": "2026-05-28T12:30:00Z",
    "updated_at": "2026-05-28T13:00:00Z"
  }
}

Ошибки

КодerrorКогда
400invalid id formatИдентификатор не является корректным UUID.
400user is not a curatorУ целевого пользователя роль не curator.
401unauthorizedТокен отсутствует, истёк или недействителен.
403forbiddenНедостаточно прав — требуется роль администратора.
GET/api/v1/t/me/profile

Мой профиль

TenantStudent JWT

Возвращает собственную учётную запись вызывающего (с правами куратора, если применимо). Доступно любому авторизованному участнику школы — ученику, куратору или админу.

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

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

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

200 OK · application/json
{
  "success": true,
  "data": {
    "id": "8f3b1c2a-1d2e-4a5b-9c6d-7e8f9a0b1c2d",
    "tenant_id": "1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
    "email": "student@acme.kz",
    "first_name": "Дамир",
    "last_name": "Сериков",
    "avatar": null,
    "role": "student",
    "is_active": true,
    "last_active_at": "2026-05-28T09:50:00Z",
    "group_count": 2,
    "groups": [
      { "group_id": "g1g1...", "name": "Поток 2026", "logo": "a1b2c3d4-0000-4000-8000-000000000000" },
      { "group_id": "g2g2...", "name": "Группа B", "logo": null }
    ],
    "created_at": "2026-05-20T10:00:00Z",
    "updated_at": "2026-05-28T09:50:00Z"
  }
}

Ошибки

КодerrorКогда
401unauthorizedТокен отсутствует, истёк или недействителен либо школа не определена.
404user not foundУчётная запись не найдена (удалена).
PATCH/api/v1/t/me/profile

Обновить мой профиль

TenantStudent JWT

Обновляет собственный профиль: имя, фамилию, аватар. Принимает два формата тела: application/json (как раньше — avatar это URL или UUID) и multipart/form-data с текстовыми полями first_name/last_name и необязательной частью-файлом avatar (загрузка изображения файлом). Поля email, role, is_active, tenant_id, password через этот эндпоинт не меняются — они молча игнорируются.

В режиме multipart часть-файл avatar загружается на сервере через media-сервис: только изображение (jpeg/png/webp/gif), реальный MIME проверяется сниффингом (а не заявленным заголовком), учитывается квота хранилища тенанта. Файл сохраняется как медиа-ассет, и в ответе avatar приходит готовым presigned-URL. Если части-файла нет, текстовое поле avatar трактуется как URL/UUID (контракт JSON-режима).

Тело запроса

ПараметрТипОписание
first_nameопц.stringИмя (1–100 символов). JSON-поле или текстовая часть multipart.
last_nameопц.stringФамилия (1–100 символов). JSON-поле или текстовая часть multipart.
avatarопц.string | fileВ JSON — URL аватара или UUID медиа-ассета. В multipart — часть-файл с изображением (загружается на сервер) либо строковое значение URL/UUID, если файла нет. Для UUID/файла на чтение возвращается presigned-URL.

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

cURL
# JSON — имя/фамилия (+ avatar как URL/UUID)
curl -X PATCH https://api.edumentor.kz/api/v1/t/me/profile \
  -H "Authorization: Bearer <token>" \
  -H "X-Tenant-Subdomain: acme" \
  -H "Content-Type: application/json" \
  -d '{ "first_name": "Дамир", "last_name": "Серик" }'

# multipart — загрузка аватара файлом
curl -X PATCH https://api.edumentor.kz/api/v1/t/me/profile \
  -H "Authorization: Bearer <token>" \
  -H "X-Tenant-Subdomain: acme" \
  -F "first_name=Дамир" \
  -F "avatar=@/path/to/avatar.png"

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

200 OK · application/json
{
  "success": true,
  "data": {
    "id": "8f3b1c2a-1d2e-4a5b-9c6d-7e8f9a0b1c2d",
    "tenant_id": "1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
    "email": "student@acme.kz",
    "first_name": "Дамир",
    "last_name": "Серик",
    "avatar": "https://storage.edumentor.kz/...?X-Amz-Signature=...",
    "role": "student",
    "is_active": true,
    "created_at": "2026-05-20T10:00:00Z",
    "updated_at": "2026-05-28T13:30:00Z"
  }
}

Ошибки

КодerrorКогда
400validation errorТело запроса не прошло валидацию (например, first_name длиннее 100 символов).
400empty_fileЧасть-файл avatar пуста (размер 0).
400invalid_image_typeЗагруженный файл — не изображение из разрешённого списка (jpeg/png/webp/gif).
400avatar_too_largeРазмер файла превышает лимит для изображений.
400storage_quota_exceededПревышена квота хранилища тенанта.
401unauthorizedТокен отсутствует, истёк или недействителен либо школа не определена.
POST/api/v1/t/me/change-password

Сменить пароль

TenantStudent JWT

Меняет собственный пароль после проверки текущего. Доступно любому авторизованному участнику школы. Действующие JWT-токены НЕ инвалидируются (упрощение v1).

Тело запроса

ПараметрТипОписание
current_passwordобяз.stringТекущий пароль (проверяется по bcrypt-хешу).
new_passwordобяз.stringНовый пароль (8–128 символов).

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

cURL
curl -X POST https://api.edumentor.kz/api/v1/t/me/change-password \
  -H "Authorization: Bearer <token>" \
  -H "X-Tenant-Subdomain: acme" \
  -H "Content-Type: application/json" \
  -d '{ "current_password": "old-secret", "new_password": "new-super-secret" }'

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

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

Ошибки

КодerrorКогда
400invalid_current_passwordТекущий пароль не совпал с хранимым хешем.
400user_account_inactiveУчётная запись деактивирована — смена пароля запрещена.
400validation errornew_password короче 8 или длиннее 128 символов.
401unauthorizedТокен отсутствует, истёк или недействителен либо школа не определена.