TenantAdminCuratorStudent

Экзамены (ЕНТ)

Многосекционные экзамены: куратор собирает экзамен из секций (квизов), ученик проходит его по секциям с единым таймером. Авторство — manage_exams, прохождение — любой участник.

GET/api/v1/t/exams

Список экзаменов

Tenant JWT

Возвращает экзамены школы. Ученикам всегда отдаются только опубликованные экзамены (черновики не утекают). Куратор/админ по умолчанию видят все статусы и могут сузить выборку параметром ?status=published|draft.

Фильтр status применяется только для кураторов/админов; для ученика он игнорируется (всегда published). Некорректное значение status — 400 invalid_status.

Query-параметры

ПараметрТипОписание
statusопц.stringТолько для куратора/админа: published | draft. У ученика игнорируется.

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

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

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

200 OK · application/json
{
  "success": true,
  "data": [
    { "id": "e1a2b3c4-...", "title": "Пробное ЕНТ — Математика", "status": "published" }
  ]
}
GET/api/v1/t/exams/:id

Экзамен с секциями

Tenant JWT

Полное представление экзамена вместе со списком секций. Ответ зависит от роли: куратор/админ получают кураторское представление, ученик — студенческое с информацией о доступе и его попытках. Каждая секция содержит question_count (число вопросов в тесте секции).

Роль-зависимый ответ. Ученик видит только опубликованные экзамены — для черновика возвращается 404 exam_not_found. В студенческом представлении к базовым полям добавлены: result — лучшая/последняя завершённая попытка согласно retake_policy ({ attempt_id, score, max_score, score_pct, passed, submitted_at, sections }) либо null; result.sections — разбивка по секциям ({ section_id, title, weight, score, max_score, score_pct } для каждой секции), чтобы показать, сколько баллов получено за каждую часть; in_progress_attempt — незавершённая попытка для возобновления ({ attempt_id, started_at, deadline_at }) либо null; attempts_count — число завершённых попыток; auth_has_access (boolean) и has_access_untill (сегодня всегда null). Кураторское представление дополнительно к каждой секции содержит question_count.

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

ПараметрТипОписание
idобяз.uuidID экзамена.

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

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

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

200 OK · application/json
{
  "success": true,
  "data": {
    "id": "e1a2b3c4-...",
    "title": "Пробное ЕНТ — Математика",
    "description": "",
    "time_limit_seconds": 14400,
    "passing_score": 50,
    "max_attempts": 3,
    "retake_policy": "best",
    "status": "published",
    "sections": [
      { "id": "5ec70001-...", "quiz_id": "9d1f...", "title": "Часть A", "position": 1, "weight": 40, "question_count": 25 }
    ],
    "result": {
      "attempt_id": "a77e0001-...",
      "score": "72.00",
      "max_score": "100.00",
      "score_pct": 72,
      "passed": true,
      "submitted_at": "2026-05-21T11:30:00Z",
      "sections": [
        {
          "section_id": "5ec70001-...",
          "title": "Часть A",
          "weight": 40,
          "score": "18.00",
          "max_score": "25.00",
          "score_pct": 72
        }
      ]
    },
    "in_progress_attempt": {
      "attempt_id": "a77e0002-...",
      "started_at": "2026-05-22T09:00:00Z",
      "deadline_at": "2026-05-22T13:00:00Z"
    },
    "attempts_count": 1,
    "auth_has_access": true,
    "has_access_untill": null,
    "created_at": "2026-05-20T10:00:00Z",
    "updated_at": "2026-05-20T10:00:00Z"
  }
}

Ошибки

КодerrorКогда
404exam_not_foundЭкзамен не найден в этой школе (или это черновик, недоступный ученику).
POST/api/v1/t/exams

Создать экзамен

AdminCurator JWT право: manage_exams

Создаёт черновик экзамена. Секции добавляются отдельными запросами.

Тело запроса

ПараметрТипОписание
titleобяз.stringНазвание (1–255).
descriptionопц.stringОписание.
time_limit_secondsобяз.integerОбщий лимит времени в секундах (> 0).Пример: 14400
passing_scoreопц.integerПроходной балл в процентах (0–100). 0 — допустимо.
max_attemptsопц.integerМаксимум попыток (> 0). Пусто — без ограничения.
retake_policyопц.stringУчитываемый результат при пересдаче: best или last.

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

cURL
curl -X POST https://api.edumentor.kz/api/v1/t/exams \
  -H "Authorization: Bearer <token>" \
  -H "X-Tenant-Subdomain: acme" \
  -H "Content-Type: application/json" \
  -d '{ "title": "Пробное ЕНТ — Математика", "time_limit_seconds": 14400, "passing_score": 50, "max_attempts": 3 }'

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

200 OK · application/json
{
  "success": true,
  "data": {
    "id": "e1a2b3c4-...",
    "title": "Пробное ЕНТ — Математика",
    "description": "",
    "time_limit_seconds": 14400,
    "passing_score": 50,
    "max_attempts": 3,
    "retake_policy": "best",
    "status": "draft",
    "sections": [
      { "id": "5ec70001-...", "quiz_id": "9d1f...", "title": "Часть A", "position": 1, "weight": 40, "question_count": 25 }
    ],
    "created_at": "2026-05-20T10:00:00Z",
    "updated_at": "2026-05-20T10:00:00Z"
  }
}

Ошибки

КодerrorКогда
400invalid_request_bodyНекорректное тело запроса.
403forbiddenНет права manage_exams.
PATCH/api/v1/t/exams/:id

Обновить экзамен

AdminCurator JWT право: manage_exams

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

Структурные поля (time_limit_seconds, passing_score, max_attempts, retake_policy) запрещено менять у опубликованного экзамена — вернётся 409 exam_published_immutable. Флаги clear_passing_score / clear_max_attempts сбрасывают соответствующие значения в null.

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

ПараметрТипОписание
idобяз.uuidID экзамена.

Тело запроса

ПараметрТипОписание
titleопц.stringНазвание.
descriptionопц.stringОписание.
time_limit_secondsопц.integerЛимит времени (> 0).
passing_scoreопц.integerПроходной балл (0–100).
clear_passing_scoreопц.booleanСбросить проходной балл в null.
max_attemptsопц.integerМаксимум попыток (> 0).
clear_max_attemptsопц.booleanСбросить лимит попыток в null.
retake_policyопц.stringbest или last.

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

cURL
curl -X PATCH https://api.edumentor.kz/api/v1/t/exams/e1a2b3c4-... \
  -H "Authorization: Bearer <token>" \
  -H "X-Tenant-Subdomain: acme" \
  -H "Content-Type: application/json" \
  -d '{ "title": "Пробное ЕНТ — Алгебра" }'

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

200 OK · application/json
{
  "success": true,
  "data": {
    "id": "e1a2b3c4-...",
    "title": "Пробное ЕНТ — Математика",
    "description": "",
    "time_limit_seconds": 14400,
    "passing_score": 50,
    "max_attempts": 3,
    "retake_policy": "best",
    "status": "draft",
    "sections": [
      { "id": "5ec70001-...", "quiz_id": "9d1f...", "title": "Часть A", "position": 1, "weight": 40, "question_count": 25 }
    ],
    "created_at": "2026-05-20T10:00:00Z",
    "updated_at": "2026-05-20T10:00:00Z"
  }
}

Ошибки

КодerrorКогда
404exam_not_foundЭкзамен не найден.
409exam_published_immutableСтруктурные поля нельзя менять у опубликованного экзамена.
DELETE/api/v1/t/exams/:id

Удалить экзамен

AdminCurator JWT право: manage_exams

Мягкое удаление экзамена.

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

ПараметрТипОписание
idобяз.uuidID экзамена.

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

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

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

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

Ошибки

КодerrorКогда
404exam_not_foundЭкзамен не найден.
409exam_has_in_progress_attemptsЕсть незавершённые попытки прохождения.
POST/api/v1/t/exams/:id/publish

Опубликовать экзамен

AdminCurator JWT право: manage_exams

Делает экзамен доступным ученикам. Требуется хотя бы одна секция.

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

ПараметрТипОписание
idобяз.uuidID экзамена.

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

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

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

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

Ошибки

КодerrorКогда
400exam_section_requiredВ экзамене нет ни одной секции.
409exam_already_publishedЭкзамен уже опубликован.
POST/api/v1/t/exams/:id/unpublish

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

AdminCurator JWT право: manage_exams

Возвращает экзамен в черновик.

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

ПараметрТипОписание
idобяз.uuidID экзамена.

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

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

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

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

Ошибки

КодerrorКогда
404exam_not_foundЭкзамен не найден.
POST/api/v1/t/exams/:id/sections

Добавить секцию

AdminCurator JWT право: manage_exams

Добавляет в экзамен секцию на основе существующего теста (квиза) с весом в общем балле.

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

ПараметрТипОписание
idобяз.uuidID экзамена.

Тело запроса

ПараметрТипОписание
quiz_idобяз.uuidID теста, который станет содержимым секции.
titleобяз.stringНазвание секции (1–255).
weightобяз.integerВес секции в общем балле (1–1000).Пример: 40

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

cURL
curl -X POST https://api.edumentor.kz/api/v1/t/exams/e1a2b3c4-.../sections \
  -H "Authorization: Bearer <token>" \
  -H "X-Tenant-Subdomain: acme" \
  -H "Content-Type: application/json" \
  -d '{ "quiz_id": "9d1f...", "title": "Часть A", "weight": 40 }'

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

200 OK · application/json
{
  "success": true,
  "data": { "id": "5ec70001-...", "quiz_id": "9d1f...", "title": "Часть A", "position": 1, "weight": 40 }
}

Ошибки

КодerrorКогда
400invalid_weightВес вне диапазона 1–1000.
404exam_not_foundЭкзамен не найден.
PATCH/api/v1/t/exams/:id/sections/:section_id

Обновить секцию

AdminCurator JWT право: manage_exams

Меняет тест, название или вес секции.

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

ПараметрТипОписание
idобяз.uuidID экзамена.
section_idобяз.uuidID секции.

Тело запроса

ПараметрТипОписание
quiz_idопц.uuidНовый тест секции.
titleопц.stringНовое название (1–255).
weightопц.integerНовый вес (1–1000).

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

cURL
curl -X PATCH https://api.edumentor.kz/api/v1/t/exams/e1a2b3c4-.../sections/5ec70001-... \
  -H "Authorization: Bearer <token>" \
  -H "X-Tenant-Subdomain: acme" \
  -H "Content-Type: application/json" \
  -d '{ "weight": 60 }'

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

200 OK · application/json
{
  "success": true,
  "data": { "id": "5ec70001-...", "quiz_id": "9d1f...", "title": "Часть A", "position": 1, "weight": 60 }
}

Ошибки

КодerrorКогда
404exam_section_not_foundСекция не найдена.
DELETE/api/v1/t/exams/:id/sections/:section_id

Удалить секцию

AdminCurator JWT право: manage_exams

Удаляет секцию из экзамена.

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

ПараметрТипОписание
idобяз.uuidID экзамена.
section_idобяз.uuidID секции.

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

cURL
curl -X DELETE https://api.edumentor.kz/api/v1/t/exams/e1a2b3c4-.../sections/5ec70001-... \
  -H "Authorization: Bearer <token>" \
  -H "X-Tenant-Subdomain: acme"

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

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

Ошибки

КодerrorКогда
404exam_section_not_foundСекция не найдена.
POST/api/v1/t/exams/:id/sections/reorder

Изменить порядок секций

AdminCurator JWT право: manage_exams

Задаёт новый порядок секций. Список должен содержать все ID секций экзамена без повторов.

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

ПараметрТипОписание
idобяз.uuidID экзамена.

Тело запроса

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

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

cURL
curl -X POST https://api.edumentor.kz/api/v1/t/exams/e1a2b3c4-.../sections/reorder \
  -H "Authorization: Bearer <token>" \
  -H "X-Tenant-Subdomain: acme" \
  -H "Content-Type: application/json" \
  -d '{ "section_ids": ["5ec70002-...", "5ec70001-..."] }'

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

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

Ошибки

КодerrorКогда
409reorder_duplicateВ списке есть повторяющиеся ID.
409reorder_incompleteСписок не содержит все секции экзамена.
GET/api/v1/t/exams/:id/analytics

Аналитика по экзамену

AdminCurator JWT право: view_analytics

Сводная статистика попыток: количество, средний балл, доля сдавших и пр.

Куратор без расширенных прав видит аналитику только по своим ученикам.

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

ПараметрТипОписание
idобяз.uuidID экзамена.

Query-параметры

ПараметрТипОписание
include_admin_attemptsопц.booleanВключить попытки админов/кураторов в агрегаты. По умолчанию только ученики.Пример: true

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

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

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

200 OK · application/json
{
  "success": true,
  "data": { "attempt_count": 42, "avg_score_pct": 64.5, "passed_count": 28 }
}

Ошибки

КодerrorКогда
404exam_not_foundЭкзамен не найден.
POST/api/v1/t/exam-attempts

Начать попытку экзамена

Student JWT

Создаёт попытку прохождения опубликованного экзамена и запускает общий таймер.

Стартовать попытку может любой авторизованный участник; роль фиксируется в actor_role и влияет на фильтрацию аналитики.

Тело запроса

ПараметрТипОписание
exam_idобяз.uuidID экзамена.

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

cURL
curl -X POST https://api.edumentor.kz/api/v1/t/exam-attempts \
  -H "Authorization: Bearer <token>" \
  -H "X-Tenant-Subdomain: acme" \
  -H "Content-Type: application/json" \
  -d '{ "exam_id": "e1a2b3c4-..." }'

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

200 OK · application/json
{
  "success": true,
  "data": {
    "id": "a77e0001-...",
    "exam_id": "e1a2b3c4-...",
    "user_id": "8f3b1c2a-...",
    "status": "in_progress",
    "actor_role": "student",
    "started_at": "2026-05-21T09:00:00Z",
    "deadline_at": "2026-05-21T13:00:00Z",
    "section_attempts": []
  }
}

Ошибки

КодerrorКогда
404exam_not_foundЭкзамен не найден.
400exam_not_publishedЭкзамен ещё не опубликован.
409max_exam_attempts_reachedДостигнут лимит попыток.
GET/api/v1/t/exam-attempts/active

Активная попытка

Student JWT

Возвращает незавершённую попытку текущего пользователя по экзамену (для возобновления).

Query-параметры

ПараметрТипОписание
exam_idобяз.uuidID экзамена.

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

cURL
curl "https://api.edumentor.kz/api/v1/t/exam-attempts/active?exam_id=e1a2b3c4-..." \
  -H "Authorization: Bearer <token>" \
  -H "X-Tenant-Subdomain: acme"

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

200 OK · application/json
{
  "success": true,
  "data": { "id": "a77e0001-...", "exam_id": "e1a2b3c4-...", "status": "in_progress" }
}

Ошибки

КодerrorКогда
404exam_attempt_not_foundАктивной попытки нет.
GET/api/v1/t/exam-attempts/:id

Попытка экзамена

AdminCuratorStudent JWT

Состояние попытки. Владелец читает свою попытку; куратор/админ — попытки своих учеников.

Не владельцу и не управляющему попытка отдаётся как 404 exam_attempt_not_found (без раскрытия существования).

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

ПараметрТипОписание
idобяз.uuidID попытки.

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

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

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

200 OK · application/json
{
  "success": true,
  "data": {
    "id": "a77e0001-...",
    "exam_id": "e1a2b3c4-...",
    "status": "submitted",
    "final_score": "72.00",
    "final_max_score": "100.00",
    "final_score_pct": "72.00",
    "passed": true,
    "section_attempts": [
      { "id": "5a000001-...", "exam_section_id": "5ec70001-...", "quiz_attempt_id": "qa00...", "position": 1 }
    ]
  }
}

Ошибки

КодerrorКогда
404exam_attempt_not_foundПопытка не найдена или недоступна.
POST/api/v1/t/exam-attempts/:id/go

Перейти к секции

Student JWT

Открывает указанную секцию и возвращает её содержимое для ученика. quiz — это студенческий вид теста (StudentQuizView): у вопросов есть points, но НЕТ правильных ответов (is_correct/correct_text убраны). Для вопросов matching правый столбец перемешан и токенизирован — у каждой опции match_rights[].id это одноразовый токен, а не реальный id (match_lefts[].id — реальные). Ответ matching отправляется как { pairs: [{ left_id, right_id }] }, где right_id — этот токен.

Раньше эндпоинт отдавал «сырой» QuizView с реальными id и ключом ответов — это и раскрывало верные ответы, и приводило к тому, что вопросы matching в экзамене всегда оценивались в 0 (грейдер ждёт токены). Теперь секция отдаёт тот же токенизированный студенческий вид, что и обычный тест, поэтому matching оценивается корректно.

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

ПараметрТипОписание
idобяз.uuidID попытки.

Тело запроса

ПараметрТипОписание
section_indexобяз.integerИндекс секции, начиная с 0.

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

cURL
curl -X POST https://api.edumentor.kz/api/v1/t/exam-attempts/a77e0001-.../go \
  -H "Authorization: Bearer <token>" \
  -H "X-Tenant-Subdomain: acme" \
  -H "Content-Type: application/json" \
  -d '{ "section_index": 0 }'

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

200 OK · application/json
{
  "success": true,
  "data": {
    "section": { "id": "5ec70001-...", "title": "Часть A", "position": 1 },
    "quiz": {
      "id": "9d1f...",
      "title": "Часть A",
      "questions": [
        {
          "id": "q1...",
          "type": "matching",
          "prompt": "Сопоставьте страну и столицу",
          "position": 0,
          "points": "2.00",
          "answers": [],
          "match_lefts": [{ "id": "m1...", "text": "Франция" }],
          "match_rights": [{ "id": "tok-9f3a...", "text": "Париж" }]
        }
      ]
    },
    "deadline_at": "2026-05-21T13:00:00Z"
  }
}

Ошибки

КодerrorКогда
400invalid_section_indexНекорректный индекс секции.
409exam_attempt_already_submittedПопытка уже завершена.
POST/api/v1/t/exam-attempts/:id/sections/:n/answers

Сохранить ответы секции

Student JWT

Сохраняет (мержит) ответы в указанной секции. Формат тела совпадает с прохождением обычного теста.

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

ПараметрТипОписание
idобяз.uuidID попытки.
nобяз.integerИндекс секции, начиная с 0.

Тело запроса

ПараметрТипОписание
answersобяз.object[]Массив ответов вида { question_id, payload }; payload зависит от типа вопроса.

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

cURL
curl -X POST https://api.edumentor.kz/api/v1/t/exam-attempts/a77e0001-.../sections/0/answers \
  -H "Authorization: Bearer <token>" \
  -H "X-Tenant-Subdomain: acme" \
  -H "Content-Type: application/json" \
  -d '{ "answers": [ { "question_id": "q1...", "payload": { "choice_id": "c2..." } } ] }'

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

200 OK · application/json
{ "success": true, "data": { "id": "a77e0001-...", "status": "in_progress" } }

Ошибки

КодerrorКогда
404exam_section_not_foundСекция не найдена.
409exam_attempt_already_submittedПопытка уже завершена.
POST/api/v1/t/exam-attempts/:id/submit

Завершить попытку

Student JWT

Завершает попытку, автоматически оценивает все секции и фиксирует итоговый балл. Тело запроса игнорируется.

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

ПараметрТипОписание
idобяз.uuidID попытки.

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

cURL
curl -X POST https://api.edumentor.kz/api/v1/t/exam-attempts/a77e0001-.../submit \
  -H "Authorization: Bearer <token>" \
  -H "X-Tenant-Subdomain: acme"

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

200 OK · application/json
{
  "success": true,
  "data": {
    "id": "a77e0001-...",
    "status": "submitted",
    "final_score": "72.00",
    "final_max_score": "100.00",
    "final_score_pct": "72.00",
    "passed": true,
    "submitted_at": "2026-05-21T11:30:00Z"
  }
}

Ошибки

КодerrorКогда
404exam_attempt_not_foundПопытка не найдена.
409exam_attempt_already_submittedПопытка уже завершена.