Тесты
Авторинг тестов и вопросов (CRUD + переупорядочивание), а также проверка попыток куратором: список попыток, детальный разбор и выставление баллов за свободные ответы.
- POST
/api/v1/t/quizzesСоздать тест - GET
/api/v1/t/quizzesСписок тестов - GET
/api/v1/t/quizzes/:idПолучить тест - PATCH
/api/v1/t/quizzes/:idОбновить тест - DELETE
/api/v1/t/quizzes/:idУдалить тест - POST
/api/v1/t/quizzes/:id/questionsДобавить вопрос - PATCH
/api/v1/t/quizzes/:id/reorder-questionsПереупорядочить вопросы - PATCH
/api/v1/t/quiz-questions/:idОбновить вопрос - DELETE
/api/v1/t/quiz-questions/:idУдалить вопрос - GET
/api/v1/t/quizzes/:id/attemptsСписок попыток теста - GET
/api/v1/t/quiz-attempts/:idДетальный разбор попытки - POST
/api/v1/t/quiz-attempts/:id/gradeВыставить баллы за попытку
/api/v1/t/quizzesСоздать тест
Создаёт новый тест в текущей школе. Опциональные поля задают политику попыток, лимит времени, проходной балл и режим начисления баллов по умолчанию.
Тело запроса
| Параметр | Тип | Описание |
|---|---|---|
titleобяз. | string | Название теста (2–255 символов). |
descriptionопц. | string | Описание (до 10000 символов). |
max_attemptsопц. | integer | Максимум попыток (1–1000). Не задано — без ограничения. |
time_limit_secondsопц. | integer | Лимит времени в секундах (10–86400). Не задано — без лимита. |
passing_scoreопц. | integer | Проходной балл в процентах (1–100). Не задано — без понятия «зачёт/незачёт». |
retake_policyопц. | string | Какая попытка считается зачётной: best или last. По умолчанию best. |
default_scoring_modeопц. | string | Режим начисления баллов по умолчанию: all_or_nothing или partial. По умолчанию all_or_nothing. |
Пример запроса
curl -X POST https://api.edumentor.kz/api/v1/t/quizzes \
-H "Authorization: Bearer <token>" \
-H "X-Tenant-Subdomain: acme" \
-H "Content-Type: application/json" \
-d '{
"title": "Тест по алгебре",
"max_attempts": 3,
"time_limit_seconds": 1800,
"passing_score": 60,
"retake_policy": "best",
"default_scoring_mode": "all_or_nothing"
}'Пример ответа
{
"success": true,
"data": {
"id": "9c1f2e3a-...",
"tenant_id": "8f3b1c2a-...",
"title": "Тест по алгебре",
"description": "",
"max_attempts": 3,
"time_limit_seconds": 1800,
"passing_score": 60,
"retake_policy": "best",
"default_scoring_mode": "all_or_nothing",
"questions": [],
"course_refs": [],
"published_via_course": false,
"exam_refs": [],
"published_via_exam": false,
"created_at": "2026-05-28T10:00:00Z",
"updated_at": "2026-05-28T10:00:00Z"
}
}Ошибки
| Код | error | Когда |
|---|---|---|
| 400 | invalid_request_body | Тело запроса не прошло валидацию (отсутствует title, неверные диапазоны и т.д.). |
| 401 | unauthorized | Токен отсутствует или недействителен. |
| 403 | forbidden | Нет права manage_courses. |
/api/v1/t/quizzesСписок тестов
Возвращает постраничный список тестов школы с агрегатами для кураторской таблицы: число вопросов, количество попыток, средний балл, число ожидающих проверки, а также контексты использования в курсах и экзаменах. Доступен любому авторизованному участнику школы.
Query-параметры
| Параметр | Тип | Описание |
|---|---|---|
qопц. | string | Поиск по названию. |
statusопц. | string | Фильтр: all, published или draft. |
course_idопц. | uuid | Фильтр по курсу, где используется тест. |
pageопц. | integer | Номер страницы (по умолчанию 1).Пример: 1 |
limitопц. | integer | Размер страницы (1–100, по умолчанию 20).Пример: 20 |
Пример запроса
curl "https://api.edumentor.kz/api/v1/t/quizzes?q=алгебра&page=1&limit=20" \
-H "Authorization: Bearer <token>" \
-H "X-Tenant-Subdomain: acme"Пример ответа
{
"success": true,
"data": {
"quizzes": [
{
"id": "9c1f2e3a-...",
"tenant_id": "8f3b1c2a-...",
"title": "Тест по алгебре",
"description": "",
"question_count": 10,
"attempts_count": 24,
"avg_score_pct": 72,
"pending_reviews_count": 3,
"course_refs": [
{
"course_id": "...",
"course_title": "Математика 9 класс",
"course_status": "published",
"module_id": "...",
"module_title": "Модуль 1",
"lesson_id": "...",
"lesson_title": "Урок 3"
}
],
"published_via_course": true,
"exam_refs": [],
"published_via_exam": false,
"created_at": "2026-05-28T10:00:00Z",
"updated_at": "2026-05-28T10:00:00Z"
}
],
"total": 1,
"page": 1,
"limit": 20
}
}Ошибки
| Код | error | Когда |
|---|---|---|
| 400 | invalid_params | Неверные значения page/limit. |
| 400 | invalid_course_id | course_id не является корректным UUID. |
| 401 | unauthorized | Токен отсутствует или недействителен. |
/api/v1/t/quizzes/:idПолучить тест
Возвращает детальный тест с вложенными вопросами, ответами и сопоставлениями. Куратор/админ получают полный вид с правильными ответами и метаданными начисления баллов.
Для роли student отдаётся редактированный вид без правильных ответов: убраны is_correct, correct_text, scoring_mode, passing_score, retake_policy. Поле points (баллы за вопрос) теперь присутствует и в студенческом виде — чтобы показать «N баллов» при прохождении. Для вопросов типа matching правый столбец перемешан, а его опции получают одноразовые токены вместо реальных id, чтобы не раскрыть верные пары.
Параметры пути
| Параметр | Тип | Описание |
|---|---|---|
idобяз. | uuid | Идентификатор теста. |
Пример запроса
curl https://api.edumentor.kz/api/v1/t/quizzes/9c1f2e3a-... \
-H "Authorization: Bearer <token>" \
-H "X-Tenant-Subdomain: acme"Пример ответа
{
"success": true,
"data": {
"id": "9c1f2e3a-...",
"tenant_id": "8f3b1c2a-...",
"title": "Тест по алгебре",
"description": "",
"max_attempts": 3,
"time_limit_seconds": 1800,
"passing_score": 60,
"retake_policy": "best",
"default_scoring_mode": "all_or_nothing",
"questions": [
{
"id": "a1b2c3d4-...",
"type": "single",
"prompt": "Сколько будет 2+2?",
"position": 0,
"points": "1.00",
"scoring_mode": null,
"answers": [
{ "id": "...", "text": "4", "is_correct": true, "position": 0 },
{ "id": "...", "text": "5", "is_correct": false, "position": 1 }
],
"correct_text": [],
"matches": []
}
],
"course_refs": [],
"published_via_course": false,
"exam_refs": [],
"published_via_exam": false,
"created_at": "2026-05-28T10:00:00Z",
"updated_at": "2026-05-28T10:00:00Z"
}
}Ошибки
| Код | error | Когда |
|---|---|---|
| 400 | invalid_id_format | id не является корректным UUID. |
| 401 | unauthorized | Токен отсутствует или недействителен. |
| 404 | quiz_not_found | Тест не найден или принадлежит другой школе. |
/api/v1/t/quizzes/:idОбновить тест
Частично обновляет тест. Все поля опциональны; переданные значения заменяют текущие, отсутствующие остаются без изменений.
Явное обнуление nullable-полей (например, снятие лимита попыток через max_attempts=null) в v1 не поддерживается.
Параметры пути
| Параметр | Тип | Описание |
|---|---|---|
idобяз. | uuid | Идентификатор теста. |
Тело запроса
| Параметр | Тип | Описание |
|---|---|---|
titleопц. | string | Название (2–255 символов). |
descriptionопц. | string | Описание (до 10000 символов). |
max_attemptsопц. | integer | Максимум попыток (1–1000). |
time_limit_secondsопц. | integer | Лимит времени в секундах (10–86400). |
passing_scoreопц. | integer | Проходной балл в процентах (1–100). |
retake_policyопц. | string | best или last. |
default_scoring_modeопц. | string | all_or_nothing или partial. |
Пример запроса
curl -X PATCH https://api.edumentor.kz/api/v1/t/quizzes/9c1f2e3a-... \
-H "Authorization: Bearer <token>" \
-H "X-Tenant-Subdomain: acme" \
-H "Content-Type: application/json" \
-d '{ "title": "Тест по алгебре (обновлён)", "passing_score": 70 }'Пример ответа
{
"success": true,
"data": {
"id": "9c1f2e3a-...",
"title": "Тест по алгебре (обновлён)",
"passing_score": 70,
"questions": []
}
}Ошибки
| Код | error | Когда |
|---|---|---|
| 400 | invalid_request_body | Тело запроса не прошло валидацию. |
| 400 | invalid_id_format | id не является корректным UUID. |
| 401 | unauthorized | Токен отсутствует или недействителен. |
| 403 | forbidden | Нет права manage_courses. |
| 404 | quiz_not_found | Тест не найден или принадлежит другой школе. |
/api/v1/t/quizzes/:idУдалить тест
Мягко удаляет тест. Возвращает 204 без тела при успехе.
Если тест используется как секция экзамена, удаление блокируется кодом 409 quiz_referenced_by_exam.
Параметры пути
| Параметр | Тип | Описание |
|---|---|---|
idобяз. | uuid | Идентификатор теста. |
Пример запроса
curl -X DELETE https://api.edumentor.kz/api/v1/t/quizzes/9c1f2e3a-... \
-H "Authorization: Bearer <token>" \
-H "X-Tenant-Subdomain: acme"Пример ответа
HTTP/1.1 204 No ContentОшибки
| Код | error | Когда |
|---|---|---|
| 400 | invalid_id_format | id не является корректным UUID. |
| 401 | unauthorized | Токен отсутствует или недействителен. |
| 403 | forbidden | Нет права manage_courses. |
| 404 | quiz_not_found | Тест не найден или принадлежит другой школе. |
| 409 | quiz_referenced_by_exam | Тест используется как секция экзамена и не может быть удалён. |
/api/v1/t/quizzes/:id/questionsДобавить вопрос
Создаёт вопрос в тесте. Поле type задаёт форму вопроса: single (один правильный ответ), multiple (несколько), free_text (свободный текст), matching (сопоставление). Варианты ответов/сопоставления добавляются отдельно через обновление вопроса.
points — десятичная строка («1.00», «2.5»), должна быть > 0; не задано — DB-дефолт «1.0». scoring_mode partial допустим только для типов multiple и matching.
Параметры пути
| Параметр | Тип | Описание |
|---|---|---|
idобяз. | uuid | Идентификатор теста. |
Тело запроса
| Параметр | Тип | Описание |
|---|---|---|
typeобяз. | string | Тип вопроса: single, multiple, free_text или matching. |
promptобяз. | string | Формулировка вопроса (1–2000 символов). |
pointsопц. | string | Вес вопроса как десятичная строка (> 0). По умолчанию «1.0». |
scoring_modeопц. | string | all_or_nothing или partial (переопределяет дефолт теста). |
Пример запроса
curl -X POST https://api.edumentor.kz/api/v1/t/quizzes/9c1f2e3a-.../questions \
-H "Authorization: Bearer <token>" \
-H "X-Tenant-Subdomain: acme" \
-H "Content-Type: application/json" \
-d '{
"type": "single",
"prompt": "Сколько будет 2+2?",
"points": "1.0"
}'Пример ответа
{
"success": true,
"data": {
"id": "a1b2c3d4-...",
"type": "single",
"prompt": "Сколько будет 2+2?",
"position": 0,
"points": "1.00",
"scoring_mode": null,
"answers": [],
"correct_text": [],
"matches": []
}
}Ошибки
| Код | error | Когда |
|---|---|---|
| 400 | invalid_request_body | Тело запроса не прошло валидацию (неизвестный type, пустой prompt и т.д.). |
| 400 | invalid_id_format | id теста не является корректным UUID. |
| 401 | unauthorized | Токен отсутствует или недействителен. |
| 403 | forbidden | Нет права manage_courses. |
| 404 | quiz_not_found | Тест не найден или принадлежит другой школе. |
/api/v1/t/quizzes/:id/reorder-questionsПереупорядочить вопросы
Атомарно переписывает позиции всех вопросов теста. ordered_ids должен быть точным множеством id всех активных вопросов теста в желаемом порядке.
Параметры пути
| Параметр | Тип | Описание |
|---|---|---|
idобяз. | uuid | Идентификатор теста. |
Тело запроса
| Параметр | Тип | Описание |
|---|---|---|
ordered_idsобяз. | string[] | Массив UUID вопросов в новом порядке (минимум 1). |
Пример запроса
curl -X PATCH https://api.edumentor.kz/api/v1/t/quizzes/9c1f2e3a-.../reorder-questions \
-H "Authorization: Bearer <token>" \
-H "X-Tenant-Subdomain: acme" \
-H "Content-Type: application/json" \
-d '{ "ordered_ids": ["a1b2c3d4-...", "e5f6a7b8-..."] }'Пример ответа
{
"success": true,
"data": { "reordered": 2 }
}Ошибки
| Код | error | Когда |
|---|---|---|
| 400 | invalid_request_body | Тело запроса не прошло валидацию. |
| 400 | invalid_id_format | id теста не является корректным UUID. |
| 401 | unauthorized | Токен отсутствует или недействителен. |
| 403 | forbidden | Нет права manage_courses. |
| 404 | quiz_not_found | Тест не найден или принадлежит другой школе. |
| 409 | reorder_duplicate | В списке есть повторяющиеся id. |
| 409 | reorder_incomplete | Список не совпадает с полным множеством вопросов теста. |
/api/v1/t/quiz-questions/:idОбновить вопрос
Частично обновляет вопрос. Поля answers, correct_text и matches имеют REPLACE-семантику: при передаче существующая коллекция полностью переписывается. Тип вопроса после создания неизменяем.
Форма payload зависит от типа вопроса: answers (text + is_correct) — для single/multiple; correct_text (массив строк) — для free_text; matches (left_text опционально, right_text обязателен и уникален) — только для matching. scoring_mode="" (пустая строка) сбрасывает переопределение к дефолту теста.
Параметры пути
| Параметр | Тип | Описание |
|---|---|---|
idобяз. | uuid | Идентификатор вопроса. |
Тело запроса
| Параметр | Тип | Описание |
|---|---|---|
promptопц. | string | Формулировка (1–2000 символов). |
answersопц. | object | Массив вариантов { text (≤500), is_correct } — для single/multiple. |
correct_textопц. | string[] | Массив эталонных строк (каждая ≤500) — для free_text. |
pointsопц. | string | Вес вопроса как десятичная строка (> 0). |
scoring_modeопц. | string | all_or_nothing, partial или "" (сброс к дефолту теста). |
matchesопц. | object | Массив пар { left_text? (≤500), right_text (1–500) } — только для matching, минимум 1. |
Пример запроса
curl -X PATCH https://api.edumentor.kz/api/v1/t/quiz-questions/a1b2c3d4-... \
-H "Authorization: Bearer <token>" \
-H "X-Tenant-Subdomain: acme" \
-H "Content-Type: application/json" \
-d '{
"prompt": "Сколько будет 2+2?",
"answers": [
{ "text": "4", "is_correct": true },
{ "text": "5", "is_correct": false }
]
}'Пример ответа
{
"success": true,
"data": {
"id": "a1b2c3d4-...",
"type": "single",
"prompt": "Сколько будет 2+2?",
"position": 0,
"points": "1.00",
"scoring_mode": null,
"answers": [
{ "id": "...", "text": "4", "is_correct": true, "position": 0 },
{ "id": "...", "text": "5", "is_correct": false, "position": 1 }
],
"correct_text": [],
"matches": []
}
}Ошибки
| Код | error | Когда |
|---|---|---|
| 400 | invalid_request_body | Тело запроса не прошло валидацию. |
| 400 | invalid_id_format | id не является корректным UUID. |
| 400 | answers_not_allowed_for_free_text | Для free_text нельзя задавать answers. |
| 400 | answers_not_allowed_for_matching | Для matching нельзя задавать answers. |
| 400 | correct_text_not_allowed_for_choice | Для single/multiple нельзя задавать correct_text. |
| 400 | single_choice_multiple_correct | Для single задано более одного правильного варианта. |
| 400 | matches_not_allowed_for_non_matching_question | matches допустимы только для типа matching. |
| 401 | unauthorized | Токен отсутствует или недействителен. |
| 403 | forbidden | Нет права manage_courses. |
| 404 | question_not_found | Вопрос не найден или принадлежит другой школе. |
/api/v1/t/quiz-questions/:idУдалить вопрос
Мягко удаляет вопрос. Возвращает 204 без тела при успехе.
Параметры пути
| Параметр | Тип | Описание |
|---|---|---|
idобяз. | uuid | Идентификатор вопроса. |
Пример запроса
curl -X DELETE https://api.edumentor.kz/api/v1/t/quiz-questions/a1b2c3d4-... \
-H "Authorization: Bearer <token>" \
-H "X-Tenant-Subdomain: acme"Пример ответа
HTTP/1.1 204 No ContentОшибки
| Код | error | Когда |
|---|---|---|
| 400 | invalid_id_format | id не является корректным UUID. |
| 401 | unauthorized | Токен отсутствует или недействителен. |
| 403 | forbidden | Нет права manage_courses. |
| 404 | question_not_found | Вопрос не найден или принадлежит другой школе. |
/api/v1/t/quizzes/:id/attemptsСписок попыток теста
Постраничный кураторский список отправленных (завершённых) попыток теста с данными ученика, баллом, процентом и флагом ожидающей проверки. Попытки, обёрнутые секцией экзамена, исключаются.
Параметры пути
| Параметр | Тип | Описание |
|---|---|---|
idобяз. | uuid | Идентификатор теста. |
Query-параметры
| Параметр | Тип | Описание |
|---|---|---|
pageопц. | integer | Номер страницы (по умолчанию 1).Пример: 1 |
limitопц. | integer | Размер страницы (1–100, по умолчанию 20).Пример: 20 |
Пример запроса
curl "https://api.edumentor.kz/api/v1/t/quizzes/9c1f2e3a-.../attempts?page=1&limit=20" \
-H "Authorization: Bearer <token>" \
-H "X-Tenant-Subdomain: acme"Пример ответа
{
"success": true,
"data": {
"attempts": [
{
"id": "11112222-...",
"student_id": "33334444-...",
"student_email": "student@acme.kz",
"student_first_name": "Алия",
"student_last_name": "Касымова",
"score": "8.00",
"max_score": "10.00",
"score_pct": 80,
"passed": true,
"started_at": "2026-05-28T09:00:00Z",
"submitted_at": "2026-05-28T09:25:00Z",
"duration_seconds": 1500,
"has_pending_review": false
}
],
"total": 1,
"page": 1,
"limit": 20
}
}Ошибки
| Код | error | Когда |
|---|---|---|
| 400 | invalid_id_format | id теста не является корректным UUID. |
| 400 | invalid_params | Неверные значения page/limit. |
| 401 | unauthorized | Токен отсутствует или недействителен. |
| 403 | forbidden | Нет права view_analytics. |
| 404 | quiz_not_found | Тест не найден или принадлежит другой школе. |
/api/v1/t/quiz-attempts/:idДетальный разбор попытки
Полный кураторский разбор попытки: заголовок попытки, авторский вид теста и по каждому вопросу — авторские правильные ответы плюс отправленный учеником payload и вердикт проверки. Используется для проверки free_text и анализа автооценки.
Параметры пути
| Параметр | Тип | Описание |
|---|---|---|
idобяз. | uuid | Идентификатор попытки. |
Пример запроса
curl https://api.edumentor.kz/api/v1/t/quiz-attempts/11112222-... \
-H "Authorization: Bearer <token>" \
-H "X-Tenant-Subdomain: acme"Пример ответа
{
"success": true,
"data": {
"attempt": {
"id": "11112222-...",
"student_id": "33334444-...",
"student_email": "student@acme.kz",
"score": "8.00",
"max_score": "10.00",
"score_pct": 80,
"passed": true,
"started_at": "2026-05-28T09:00:00Z",
"submitted_at": "2026-05-28T09:25:00Z",
"duration_seconds": 1500,
"has_pending_review": true
},
"quiz": {
"id": "9c1f2e3a-...",
"title": "Тест по алгебре",
"passing_score": 60,
"questions": []
},
"questions": [
{
"id": "a1b2c3d4-...",
"type": "free_text",
"prompt": "Объясните теорему Пифагора.",
"position": 0,
"points": "2.00",
"scoring_mode": null,
"answers": [],
"correct_text": ["a^2 + b^2 = c^2"],
"matches": [],
"student_answer_payload": { "text": "Сумма квадратов катетов..." },
"is_correct": false,
"awarded_points": "0.00"
}
]
}
}Ошибки
| Код | error | Когда |
|---|---|---|
| 400 | invalid_id_format | id не является корректным UUID. |
| 401 | unauthorized | Токен отсутствует или недействителен. |
| 403 | forbidden | Нет права view_analytics. |
| 404 | attempt_not_found | Попытка не найдена, принадлежит другой школе или обёрнута экзаменом. |
/api/v1/t/quiz-attempts/:id/gradeВыставить баллы за попытку
Сохраняет баллы и комментарии куратора по вопросам типа free_text и пересчитывает итоговый балл попытки (сумма awarded_points по всем ответам) и флаг passed, если у теста задан проходной балл. Баллы для автооцениваемых вопросов игнорируются — их нельзя переопределить.
Параметры пути
| Параметр | Тип | Описание |
|---|---|---|
idобяз. | uuid | Идентификатор попытки. |
Тело запроса
| Параметр | Тип | Описание |
|---|---|---|
gradesобяз. | object | Массив { question_id (uuid), awarded_points (десятичная строка), feedback? (≤4000) }; минимум 1. |
Пример запроса
curl -X POST https://api.edumentor.kz/api/v1/t/quiz-attempts/11112222-.../grade \
-H "Authorization: Bearer <token>" \
-H "X-Tenant-Subdomain: acme" \
-H "Content-Type: application/json" \
-d '{
"grades": [
{
"question_id": "a1b2c3d4-...",
"awarded_points": "2.0",
"feedback": "Верно по сути."
}
]
}'Пример ответа
{
"success": true,
"data": {
"attempt": {
"id": "11112222-...",
"student_id": "33334444-...",
"student_email": "student@acme.kz",
"score": "10.00",
"max_score": "10.00",
"score_pct": 100,
"passed": true,
"started_at": "2026-05-28T09:00:00Z",
"submitted_at": "2026-05-28T09:25:00Z",
"duration_seconds": 1500,
"has_pending_review": false
}
}
}Ошибки
| Код | error | Когда |
|---|---|---|
| 400 | invalid_request_body | Тело запроса не прошло валидацию. |
| 400 | invalid_id_format | id не является корректным UUID. |
| 400 | attempt_not_submitted | Попытка не завершена — проверять можно только отправленные попытки. |
| 400 | invalid_grade | awarded_points не число, вне диапазона [0, max] или указан неизвестный question_id. |
| 401 | unauthorized | Токен отсутствует или недействителен. |
| 403 | forbidden | Нет права manage_courses. |
| 404 | attempt_not_found | Попытка не найдена, принадлежит другой школе или обёрнута экзаменом. |