TenantAdminCuratorStudent

Задания

CRUD заданий, адресная выдача заданий группам, прикреплённые преподавателем файлы и жизненный цикл работ студента: создание/получение черновика, редактирование, отправка (с несколькими файлами), история попыток, проверка и переоткрытие. Все эндпоинты живут под /api/v1/t/* и требуют JWT + заголовок X-Tenant-Subdomain.

GET/api/v1/t/assignments

Список заданий

Tenant JWT

Постраничный список заданий школы. Доступен любому авторизованному пользователю школы. Списочные строки не гидрируют вложения (attachments остаётся пустым), но содержат screenshot_protection_enabled для мобильного клиента. Для роли student список фильтруется по доступу через группы, а каждая строка дополняется полями my_submission_status и my_submission_score — статусом (draft/submitted/accepted/rejected) и оценкой работы текущего студента.

Конверт ответа: { assignments, total, page, limit }. page с 1; по умолчанию page=1, limit=20 (1..100). Поля my_submission_status (draft/submitted/accepted/rejected) / my_submission_score присутствуют только в ответе студенту (для остальных ролей опускаются).

Query-параметры

ПараметрТипОписание
qопц.stringПоиск по названию задания.
pageопц.integerНомер страницы (с 1). По умолчанию 1.Пример: 1
limitопц.integerРазмер страницы (1..100). По умолчанию 20.Пример: 20

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

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

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

200 OK · application/json
{
  "success": true,
  "data": {
    "assignments": [
      {
        "id": "3c3c...",
        "title": "Эссе по главе 1",
        "instructions": "<p>Напишите эссе...</p>",
        "max_score": "100",
        "max_attempts": null,
        "screenshot_protection_enabled": true,
        "attachments": [],
        "my_submission_status": "submitted",
        "my_submission_score": null,
        "created_at": "2026-05-28T09:00:00Z",
        "updated_at": "2026-05-28T09:00:00Z"
      }
    ],
    "total": 1,
    "page": 1,
    "limit": 20
  }
}

Ошибки

КодerrorКогда
400invalid_paramsНекорректные page/limit (page<1, limit<1 или limit>100).
401unauthorizedШкола не определена или токен недействителен.
GET/api/v1/t/assignments/progress

Прогресс по заданиям (студент)

Student JWT

Возвращает агрегат прогресса текущего студента по заданиям: accessible — доступные студенту задания (без таргетинга на группы либо студент в целевой группе), completed — из них с хотя бы одной отправленной работой (статус не draft: submitted/accepted/rejected), percent — процент выполнения. Прогресс считается по факту отправки, а не по решению проверки. Только для роли student.

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

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

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

200 OK · application/json
{
  "success": true,
  "data": { "completed": 3, "accessible": 5, "percent": 60 }
}

Ошибки

КодerrorКогда
401unauthorizedШкола не определена или токен недействителен.
403student_onlyЭндпоинт доступен только роли student.
GET/api/v1/t/assignments/:id

Получить задание

Tenant JWT

Возвращает задание с screenshot_protection_enabled и гидрированным списком прикреплённых преподавателем файлов (имя, размер, тип, mime). Кросс-тенантный ID отдаётся как 404.

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

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

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

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

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

200 OK · application/json
{
  "success": true,
  "data": {
    "id": "3c3c...",
    "title": "Эссе по главе 1",
    "instructions": "<p>Напишите эссе...</p>",
    "max_score": "100",
    "max_attempts": 3,
    "screenshot_protection_enabled": true,
    "attachments": [
      {
        "id": "a1a1...",
        "media_asset_id": "m1m1...",
        "position": 0,
        "filename": "rubric.pdf",
        "size": 20480,
        "kind": "document",
        "mime": "application/pdf"
      }
    ],
    "created_at": "2026-05-28T09:00:00Z",
    "updated_at": "2026-05-28T09:00:00Z"
  }
}

Ошибки

КодerrorКогда
400invalid_id_formatID в пути не является UUID.
401unauthorizedШкола не определена или токен недействителен.
404assignment_not_foundЗадание отсутствует или принадлежит другой школе.
POST/api/v1/t/assignments

Создать задание

AdminCurator JWT право: manage_courses

Создаёт новое задание. instructions — необработанный HTML, очищается на сервере. max_score — числовая строка (например, «100» или «50.5») для сохранения точности NUMERIC. max_attempts — лимит попыток на одного студента (1..1000); если поле не передано, попытки не ограничены. Защита от скриншотов по умолчанию отключена.

201 Created.

Тело запроса

ПараметрТипОписание
titleобяз.stringНазвание задания (1..255).
instructionsопц.stringHTML-инструкции (очищаются сервером).
max_scoreопц.stringМаксимальный балл как числовая строка.Пример: 100
max_attemptsопц.integerЛимит попыток на студента (1..1000). По умолчанию — без ограничений.Пример: 3
screenshot_protection_enabledопц.booleantrue — мобильное приложение должно блокировать скриншоты. По умолчанию false.

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

cURL
curl -X POST https://api.edumentor.kz/api/v1/t/assignments \
  -H "Authorization: Bearer <token>" \
  -H "X-Tenant-Subdomain: acme" \
  -H "Content-Type: application/json" \
  -d '{ "title": "Эссе по главе 1", "instructions": "<p>Напишите эссе...</p>", "max_score": "100", "max_attempts": 3, "screenshot_protection_enabled": true }'

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

200 OK · application/json
{
  "success": true,
  "data": {
    "id": "3c3c...",
    "title": "Эссе по главе 1",
    "instructions": "<p>Напишите эссе...</p>",
    "max_score": "100",
    "max_attempts": 3,
    "screenshot_protection_enabled": true,
    "attachments": [],
    "created_at": "2026-05-28T09:00:00Z",
    "updated_at": "2026-05-28T09:00:00Z"
  }
}

Ошибки

КодerrorКогда
400invalid_request_bodyНекорректное тело запроса (нарушены binding-правила).
401unauthorizedШкола не определена или токен недействителен.
403permission_deniedНет права manage_courses.
PATCH/api/v1/t/assignments/:id

Изменить задание

AdminCurator JWT право: manage_courses

Частичное обновление задания. Переданные поля заменяют значения; отсутствующие остаются прежними. max_attempts задаёт фиксированный лимит, а clear_max_attempts=true возвращает режим без ограничений. Защиту от скриншотов можно менять независимо для каждого задания.

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

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

Тело запроса

ПараметрТипОписание
titleопц.stringНовое название (1..255).
instructionsопц.stringНовые HTML-инструкции.
max_scoreопц.stringНовый максимальный балл как числовая строка.
max_attemptsопц.integerНовый лимит попыток на студента (1..1000).
clear_max_attemptsопц.booleantrue — снять лимит и вернуть бесконечное количество попыток.
screenshot_protection_enabledопц.booleantrue — блокировать скриншоты в мобильном приложении; false — разрешить.

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

cURL
curl -X PATCH https://api.edumentor.kz/api/v1/t/assignments/3c3c... \
  -H "Authorization: Bearer <token>" \
  -H "X-Tenant-Subdomain: acme" \
  -H "Content-Type: application/json" \
  -d '{ "max_score": "80", "max_attempts": 3, "screenshot_protection_enabled": false }'

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

200 OK · application/json
{
  "success": true,
  "data": {
    "id": "3c3c...",
    "title": "Эссе по главе 1",
    "instructions": "<p>Напишите эссе...</p>",
    "max_score": "80",
    "max_attempts": 3,
    "screenshot_protection_enabled": false,
    "attachments": [],
    "created_at": "2026-05-28T09:00:00Z",
    "updated_at": "2026-05-28T12:00:00Z"
  }
}

Ошибки

КодerrorКогда
400invalid_id_formatID в пути не является UUID.
400invalid_request_bodyНекорректное тело или невалидный max_score.
401unauthorizedШкола не определена или токен недействителен.
403permission_deniedНет права manage_courses.
404assignment_not_foundЗадание отсутствует или принадлежит другой школе.
DELETE/api/v1/t/assignments/:id

Удалить задание

AdminCurator JWT право: manage_courses

Мягко удаляет задание. 204 No Content при успехе.

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

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

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

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

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

200 OK · application/json
HTTP/1.1 204 No Content

Ошибки

КодerrorКогда
400invalid_id_formatID в пути не является UUID.
401unauthorizedШкола не определена или токен недействителен.
403permission_deniedНет права manage_courses.
404assignment_not_foundЗадание отсутствует или принадлежит другой школе.
POST/api/v1/t/assignments/:id/attachments

Прикрепить файл к заданию

AdminCurator JWT право: manage_courses

Прикрепляет существующий медиа-актив к заданию (файл преподавателя — отдельно от вложений работ студентов). position необязателен — при отсутствии берётся max(position)+1.

201 Created с гидрированным AssignmentAttachmentDTO.

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

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

Тело запроса

ПараметрТипОписание
media_asset_idобяз.uuidID медиа-актива из библиотеки.
positionопц.integerПозиция в списке (>=0). По умолчанию — в конец.

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

cURL
curl -X POST https://api.edumentor.kz/api/v1/t/assignments/3c3c.../attachments \
  -H "Authorization: Bearer <token>" \
  -H "X-Tenant-Subdomain: acme" \
  -H "Content-Type: application/json" \
  -d '{ "media_asset_id": "m1m1...", "position": 0 }'

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

200 OK · application/json
{
  "success": true,
  "data": {
    "id": "a1a1...",
    "media_asset_id": "m1m1...",
    "position": 0,
    "filename": "rubric.pdf",
    "size": 20480,
    "kind": "document",
    "mime": "application/pdf"
  }
}

Ошибки

КодerrorКогда
400invalid_id_formatID в пути не является UUID.
400invalid_request_bodyНекорректное тело запроса.
400attachment_not_in_tenantМедиа-актив принадлежит другой школе.
401unauthorizedШкола не определена или токен недействителен.
403permission_deniedНет права manage_courses.
404assignment_not_foundЗадание отсутствует или принадлежит другой школе.
409attachment_duplicateЭтот медиа-актив уже прикреплён к заданию.
DELETE/api/v1/t/assignments/:id/attachments/:attachmentId

Открепить файл от задания

AdminCurator JWT право: manage_courses

Удаляет прикреплённый к заданию файл. 204 No Content при успехе.

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

ПараметрТипОписание
idобяз.uuidID задания.
attachmentIdобяз.uuidID вложения задания.

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

cURL
curl -X DELETE https://api.edumentor.kz/api/v1/t/assignments/3c3c.../attachments/a1a1... \
  -H "Authorization: Bearer <token>" \
  -H "X-Tenant-Subdomain: acme"

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

200 OK · application/json
HTTP/1.1 204 No Content

Ошибки

КодerrorКогда
400invalid_id_formatID задания или вложения не является UUID.
401unauthorizedШкола не определена или токен недействителен.
403permission_deniedНет права manage_courses.
404assignment_not_foundЗадание отсутствует или принадлежит другой школе.
404attachment_not_foundВложение не найдено.
GET/api/v1/t/assignments/:id/attachments/:attachmentId/download

Скачать файл задания

TenantAdminCuratorStudent JWT

Возвращает временную (presigned) ссылку на скачивание прикреплённого к заданию файла. Доступ — менеджер курсов (admin/superadmin или куратор с manage_courses) ИЛИ студент, записанный на курс с этим заданием.

Проверка прав выполняется внутри обработчика (а не на уровне middleware), чтобы записанные студенты тоже проходили. Самостоятельные задания (не привязанные к блокам курса) скачивают только менеджеры курсов.

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

ПараметрТипОписание
idобяз.uuidID задания.
attachmentIdобяз.uuidID вложения задания.

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

cURL
curl "https://api.edumentor.kz/api/v1/t/assignments/3c3c.../attachments/a1a1.../download" \
  -H "Authorization: Bearer <token>" \
  -H "X-Tenant-Subdomain: acme"

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

200 OK · application/json
{
  "success": true,
  "data": "https://s3.edumentor.kz/media/...&X-Amz-Signature=..."
}

Ошибки

КодerrorКогда
400invalid_id_formatID задания или вложения не является UUID.
401unauthorizedШкола не определена или токен недействителен.
403not_authorizedВызывающий не менеджер курсов и не записан на курс с этим заданием.
404assignment_not_foundЗадание отсутствует или принадлежит другой школе.
404attachment_not_foundВложение не найдено.
GET/api/v1/t/assignments/:id/group-targets

Целевые группы задания

AdminCurator JWT право: manage_courses

Возвращает список UUID групп, которым адресовано задание (allow-list). Пустой массив означает, что задание доступно всем зачисленным студентам.

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

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

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

cURL
curl https://api.edumentor.kz/api/v1/t/assignments/3c3c.../group-targets \
  -H "Authorization: Bearer <token>" \
  -H "X-Tenant-Subdomain: acme"

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

200 OK · application/json
{
  "success": true,
  "data": { "group_ids": ["g1a2b3c4-...", "g2b3c4d5-..."] }
}

Ошибки

КодerrorКогда
400invalid_id_formatID в пути не является UUID.
401unauthorizedШкола не определена или токен недействителен.
403permission_deniedНет права manage_courses.
404assignment_not_foundЗадание отсутствует или принадлежит другой школе.
PUT/api/v1/t/assignments/:id/group-targets

Задать целевые группы задания

AdminCurator JWT право: manage_courses

Атомарно перезаписывает allow-list групп задания. Если задана хотя бы одна группа — сдавать задание могут только студенты из этих групп (иначе сервер вернёт 403 при создании работы); пустой или опущенный массив открывает задание всем зачисленным.

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

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

Тело запроса

ПараметрТипОписание
group_idsопц.string[]Список UUID целевых групп (пустой/опущенный — открыть всем).

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

cURL
curl -X PUT https://api.edumentor.kz/api/v1/t/assignments/3c3c.../group-targets \
  -H "Authorization: Bearer <token>" \
  -H "X-Tenant-Subdomain: acme" \
  -H "Content-Type: application/json" \
  -d '{ "group_ids": ["g1a2b3c4-..."] }'

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

200 OK · application/json
{
  "success": true,
  "data": { "group_ids": ["g1a2b3c4-..."] }
}

Ошибки

КодerrorКогда
400invalid_id_formatID в пути не является UUID.
400invalid_request_bodyТело запроса не прошло валидацию.
401unauthorizedШкола не определена или токен недействителен.
403permission_deniedНет права manage_courses.
404assignment_not_foundЗадание отсутствует или принадлежит другой школе.
POST/api/v1/t/assignments/:id/submissions

Создать или получить черновик работы

Student JWT

Студент создаёт (или получает существующий) черновик работы по заданию. student_id берётся из JWT, не из тела. Если задание адресовано конкретным группам, а студент не входит ни в одну из них — 403. Возвращает 201 с полным SubmissionResponse.

Можно сразу приложить файлы: при теле multipart/form-data сервер загружает файлы (поле attachments) и привязывает их к черновику. Повторные попытки (first-class attempts): если текущая работа отклонена (status=rejected), вызов создаёт НОВУЮ попытку-черновик с новым id и attempt += 1 — отклонённая попытка сохраняется как история (полная история на .../submissions/history). При достижении assignment.max_attempts новая попытка не создаётся и возвращается 409 max_attempts_reached; null означает без ограничений. Статусы draft/submitted/accepted возвращаются как есть (тот же id), без создания новой попытки. JSON-тело с attachment_ids также принимается (обратная совместимость).

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

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

Тело запроса

ПараметрТипОписание
attachmentsопц.file[]Файлы (multipart/form-data) — загружаются и сразу привязываются к черновику. Действует лимит SubmissionMaxFiles.
attachment_idsопц.string[]Альтернатива multipart: список UUID предзагруженных медиа-активов (для JSON-тела).

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

cURL
# Пустой черновик
curl -X POST https://api.edumentor.kz/api/v1/t/assignments/3c3c.../submissions \
  -H "Authorization: Bearer <token>" \
  -H "X-Tenant-Subdomain: acme"

# Сразу с файлами (multipart, можно несколько — до 5)
curl -X POST https://api.edumentor.kz/api/v1/t/assignments/3c3c.../submissions \
  -H "Authorization: Bearer <token>" \
  -H "X-Tenant-Subdomain: acme" \
  -F "attachments=@/path/to/essay.pdf" \
  -F "attachments=@/path/to/appendix.pdf"

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

200 OK · application/json
{
  "success": true,
  "data": {
    "id": "5b5b...",
    "assignment_id": "3c3c...",
    "student_id": "8f3b...",
    "attempt": 1,
    "status": "draft",
    "score": null,
    "feedback": null,
    "graded_by_id": null,
    "graded_at": null,
    "submitted_at": null,
    "attachments": [],
    "created_at": "2026-05-28T10:00:00Z",
    "updated_at": "2026-05-28T10:00:00Z"
  }
}

Ошибки

КодerrorКогда
400invalid_id_formatID задания в пути не является UUID.
401unauthorizedШкола не определена или токен недействителен.
403student_not_in_targeted_groupЗадание адресовано конкретным группам, а студент не входит ни в одну из них.
404assignment_not_foundЗадание отсутствует или принадлежит другой школе.
409max_attempts_reachedСтудент уже использовал все разрешённые попытки по заданию.
GET/api/v1/t/assignments/:id/submissions

Список работ по заданию

AdminCuratorStudent JWT

Ролевой список работ по заданию. Менеджер (admin/superadmin или куратор с manage_students) получает полный рабочий лист: непривилегированный куратор видит только работы своих студентов, admin/superadmin/привилегированный куратор — все работы. Студент (и любой без manage_students) получает только собственную работу по этому заданию — в той же обёртке { submissions, total } (пустой список, если своей работы ещё нет).

Проверка прав ролевая и выполняется внутри обработчика (а не жёстким manage_students-гейтом на маршруте): наличие manage_students определяет режим (полный рабочий лист vs. только своя работа). Для строк рабочего листа менеджеру SubmissionResponse дополнительно содержит student_email / student_first_name / student_last_name; в режиме студента эти поля опускаются. Каждое вложение (SubmissionAttachmentDTO) теперь гидрируется временной ссылкой: filename, presigned_url, expires_in (секунды) — отдельный запрос к /media не нужен.

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

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

Query-параметры

ПараметрТипОписание
statusопц.stringФильтр по статусу работы: draft | submitted | accepted | rejected.
limitопц.integerРазмер страницы. По умолчанию 50.Пример: 50
offsetопц.integerСмещение. По умолчанию 0.Пример: 0

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

cURL
curl "https://api.edumentor.kz/api/v1/t/assignments/3c3c.../submissions?status=accepted&limit=50&offset=0" \
  -H "Authorization: Bearer <token>" \
  -H "X-Tenant-Subdomain: acme"

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

200 OK · application/json
{
  "success": true,
  "data": {
    "submissions": [
      {
        "id": "5b5b...",
        "assignment_id": "3c3c...",
        "student_id": "8f3b...",
        "student_email": "student@acme.kz",
        "student_first_name": "Айгерим",
        "student_last_name": "Нурлан",
        "status": "submitted",
        "score": null,
        "feedback": null,
        "graded_by_id": null,
        "graded_at": null,
        "submitted_at": "2026-05-28T11:00:00Z",
        "attachments": [
          {
            "id": "att1...",
            "media_asset_id": "m2m2...",
            "position": 0,
            "filename": "essay.pdf",
            "presigned_url": "https://s3.edumentor.kz/media/...&X-Amz-Signature=...",
            "expires_in": 900
          }
        ],
        "created_at": "2026-05-28T10:00:00Z",
        "updated_at": "2026-05-28T11:00:00Z"
      }
    ],
    "total": 1
  }
}

Ошибки

КодerrorКогда
400invalid_id_formatID задания в пути не является UUID.
401unauthorizedШкола не определена или токен недействителен.
404assignment_not_foundЗадание отсутствует или принадлежит другой школе.
GET/api/v1/t/assignments/:id/submissions/history

История попыток по заданию

AdminCuratorStudent JWT

Возвращает ПОЛНУЮ историю попыток по заданию (first-class attempts): каждая попытка — отдельная строка со своим номером attempt. Это read-путь для «показать прошлые попытки и их фидбэк». Ролевой: студент видит только свои попытки; менеджер (manage_students) — историю конкретного студента через ?student_id, либо, без параметра, попытки всех студентов. Непривилегированный куратор ограничен своим набором my_students. Каждая работа дополнена полем attempt (1-based номер попытки студента, 1 = самая ранняя); список отсортирован новейшие-сначала.

Конверт ответа: { attempts, total }. Каждая попытка имеет полный набор полей (status/score/feedback/attachments с presigned-ссылками) — её файлы сохраняются. Балл и фидбэк отклонённой работы доступны здесь даже после того, как студент начал новую попытку. Проверка прав ролевая и выполняется внутри обработчика (нет жёсткого manage_students-гейта на маршруте), чтобы студент мог увидеть собственную историю.

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

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

Query-параметры

ПараметрТипОписание
student_idопц.uuidТолько для менеджера (manage_students): ограничить историю одним студентом. Для студента игнорируется — всегда возвращается собственная история.

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

cURL
curl "https://api.edumentor.kz/api/v1/t/assignments/3c3c.../submissions/history" \
  -H "Authorization: Bearer <token>" \
  -H "X-Tenant-Subdomain: acme"

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

200 OK · application/json
{
  "success": true,
  "data": {
    "attempts": [
      {
        "id": "5b5b...",
        "assignment_id": "3c3c...",
        "student_id": "8f3b...",
        "status": "draft",
        "attempt": 2,
        "score": null,
        "feedback": null,
        "graded_by_id": null,
        "graded_at": null,
        "submitted_at": null,
        "attachments": [],
        "created_at": "2026-06-24T10:00:00Z",
        "updated_at": "2026-06-24T10:00:00Z"
      },
      {
        "id": "4a4a...",
        "assignment_id": "3c3c...",
        "student_id": "8f3b...",
        "status": "rejected",
        "attempt": 1,
        "score": "40",
        "feedback": "<p>Доработайте раздел 2</p>",
        "graded_by_id": "c1c1...",
        "graded_at": "2026-06-22T12:00:00Z",
        "submitted_at": "2026-06-21T09:00:00Z",
        "attachments": [
          {
            "id": "att0...",
            "media_asset_id": "m1m1...",
            "position": 0,
            "filename": "v1.pdf",
            "presigned_url": "https://s3.edumentor.kz/media/...&X-Amz-Signature=...",
            "expires_in": 900
          }
        ],
        "created_at": "2026-06-20T09:00:00Z",
        "updated_at": "2026-06-22T12:00:00Z"
      }
    ],
    "total": 2
  }
}

Ошибки

КодerrorКогда
400invalid_id_formatID задания или student_id не является UUID.
401unauthorizedШкола не определена или токен недействителен.
404assignment_not_foundЗадание отсутствует или принадлежит другой школе.
GET/api/v1/t/submissions/:id

Получить работу

AdminCuratorStudent JWT

Возвращает одну работу. Доступ: владелец (студент) ИЛИ менеджер студентов (manage_students). Для непривилегированного куратора работа вне его охвата схлопывается в 404.

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

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

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

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

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

200 OK · application/json
{
  "success": true,
  "data": {
    "id": "5b5b...",
    "assignment_id": "3c3c...",
    "student_id": "8f3b...",
    "status": "submitted",
    "score": null,
    "feedback": null,
    "graded_by_id": null,
    "graded_at": null,
    "submitted_at": "2026-05-28T11:00:00Z",
    "attachments": [
      {
        "id": "att1...",
        "media_asset_id": "m2m2...",
        "position": 0,
        "filename": "essay.pdf",
        "presigned_url": "https://s3.edumentor.kz/media/...&X-Amz-Signature=...",
        "expires_in": 900
      }
    ],
    "created_at": "2026-05-28T10:00:00Z",
    "updated_at": "2026-05-28T11:00:00Z"
  }
}

Ошибки

КодerrorКогда
400invalid_id_formatID в пути не является UUID.
401unauthorizedШкола не определена или токен недействителен.
404submission_not_foundРабота отсутствует, другая школа или вне охвата (схлопывание для приватности).
PATCH/api/v1/t/submissions/:id

Изменить черновик работы

Student JWT

Студент обновляет список вложений своего черновика. Доступ только владельца; статус должен быть draft. Перезапись полностью заменяет список вложений.

Принимается ЛИБО JSON-тело с attachment_ids (предзагруженные media UUID), ЛИБО multipart/form-data с самими файлами (любое имя поля для файловых частей) — в этом случае сервер загружает файлы и использует их как итоговый набор вложений. ВАЖНО: если attachment_ids НЕ переданы (поле отсутствует или multipart без файлов), текущие вложения черновика СОХРАНЯЮТСЯ — отправка с пустым телом больше не стирает уже приложенные файлы. Явный пустой массив [] очищает список. МОЖНО ПРИКРЕПИТЬ НЕСКОЛЬКО ФАЙЛОВ за один запрос — до SubmissionMaxFiles (по умолчанию 5, переменная окружения SUBMISSION_MAX_FILES): в multipart повторите файловое поле для каждого файла (-F "attachments=@a.pdf" -F "attachments=@b.pdf"; имя поля любое — сервер собирает ВСЕ файловые части), либо передайте массив из нескольких UUID в attachment_ids (JSON). Превышение лимита → 400 too_many_attachments.

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

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

Тело запроса

ПараметрТипОписание
attachment_idsопц.string[]Список UUID медиа-активов — итоговый набор вложений черновика (для JSON-тела).

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

cURL
# Вариант 1 — JSON с предзагруженными media UUID
curl -X PATCH https://api.edumentor.kz/api/v1/t/submissions/5b5b... \
  -H "Authorization: Bearer <token>" \
  -H "X-Tenant-Subdomain: acme" \
  -H "Content-Type: application/json" \
  -d '{ "attachment_ids": ["m2m2..."] }'

# Вариант 2 — multipart/form-data с самими файлами
curl -X PATCH https://api.edumentor.kz/api/v1/t/submissions/5b5b... \
  -H "Authorization: Bearer <token>" \
  -H "X-Tenant-Subdomain: acme" \
  -F "files=@/path/to/file.pdf"

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

200 OK · application/json
{
  "success": true,
  "data": {
    "id": "5b5b...",
    "assignment_id": "3c3c...",
    "student_id": "8f3b...",
    "status": "draft",
    "score": null,
    "feedback": null,
    "graded_by_id": null,
    "graded_at": null,
    "submitted_at": null,
    "attachments": [
      {
        "id": "att1...",
        "media_asset_id": "m2m2...",
        "position": 0,
        "filename": "essay.pdf",
        "presigned_url": "https://s3.edumentor.kz/media/...&X-Amz-Signature=...",
        "expires_in": 900
      }
    ],
    "created_at": "2026-05-28T10:00:00Z",
    "updated_at": "2026-05-28T10:30:00Z"
  }
}

Ошибки

КодerrorКогда
400invalid_id_formatID в пути не является UUID.
400invalid_request_bodyНекорректное тело запроса.
400attachment_not_in_tenantВложение принадлежит другой школе.
401unauthorizedШкола не определена или токен недействителен.
404submission_not_foundРабота отсутствует или не принадлежит вызывающему.
422submission_not_in_draftРабота уже не в статусе черновика — редактирование запрещено.
POST/api/v1/t/submissions/:id/submit

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

Student JWT

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

Принимается ЛИБО JSON-тело с attachment_ids (предзагруженные media UUID), ЛИБО multipart/form-data с самими файлами (любое имя поля для файловых частей) — в этом случае сервер загружает файлы и использует их как итоговый набор вложений. ВАЖНО: если attachment_ids НЕ переданы (поле отсутствует или multipart без файлов), текущие вложения черновика СОХРАНЯЮТСЯ — отправка с пустым телом больше не стирает уже приложенные файлы. Явный пустой массив [] очищает список. МОЖНО ПРИКРЕПИТЬ НЕСКОЛЬКО ФАЙЛОВ за один запрос — до SubmissionMaxFiles (по умолчанию 5, переменная окружения SUBMISSION_MAX_FILES): в multipart повторите файловое поле для каждого файла (-F "attachments=@a.pdf" -F "attachments=@b.pdf"; имя поля любое — сервер собирает ВСЕ файловые части), либо передайте массив из нескольких UUID в attachment_ids (JSON). Превышение лимита → 400 too_many_attachments.

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

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

Тело запроса

ПараметрТипОписание
attachmentsопц.file[]Multipart-вариант: один или НЕСКОЛЬКО файлов (до SubmissionMaxFiles, по умолчанию 5). Повторите поле для каждого файла; имя поля любое — сервер собирает все файловые части.
attachment_idsопц.string[]Финальный список UUID вложений на момент отправки (для JSON-тела). Можно передать несколько UUID.

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

cURL
# Вариант 1 — JSON с НЕСКОЛЬКИМИ предзагруженными media UUID
curl -X POST https://api.edumentor.kz/api/v1/t/submissions/5b5b.../submit \
  -H "Authorization: Bearer <token>" \
  -H "X-Tenant-Subdomain: acme" \
  -H "Content-Type: application/json" \
  -d '{ "attachment_ids": ["m2m2...", "m3m3...", "m4m4..."] }'

# Вариант 2 — multipart/form-data, НЕСКОЛЬКО файлов (до 5)
# повторите -F для каждого файла; имя поля любое
curl -X POST https://api.edumentor.kz/api/v1/t/submissions/5b5b.../submit \
  -H "Authorization: Bearer <token>" \
  -H "X-Tenant-Subdomain: acme" \
  -F "attachments=@/path/to/essay.pdf" \
  -F "attachments=@/path/to/appendix.pdf" \
  -F "attachments=@/path/to/photo.jpg"

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

200 OK · application/json
{
  "success": true,
  "data": {
    "id": "5b5b...",
    "assignment_id": "3c3c...",
    "student_id": "8f3b...",
    "status": "submitted",
    "score": null,
    "feedback": null,
    "graded_by_id": null,
    "graded_at": null,
    "submitted_at": "2026-05-28T11:00:00Z",
    "attachments": [
      {
        "id": "att1...",
        "media_asset_id": "m2m2...",
        "position": 0,
        "filename": "essay.pdf",
        "presigned_url": "https://s3.edumentor.kz/media/...&X-Amz-Signature=...",
        "expires_in": 900
      }
    ],
    "created_at": "2026-05-28T10:00:00Z",
    "updated_at": "2026-05-28T11:00:00Z"
  }
}

Ошибки

КодerrorКогда
400invalid_id_formatID в пути не является UUID.
400invalid_request_bodyНекорректное тело запроса.
400too_many_attachmentsПревышен лимит количества файлов в работе.
401unauthorizedШкола не определена или токен недействителен.
403schedule_window_closedУрок задания вне окна доступа по расписанию, и правило «первой попытки» не применимо (студент не открывал работу до дедлайна). Отклонённую до дедлайна работу можно перезагрузить и после него.
404submission_not_foundРабота отсутствует или не принадлежит вызывающему.
422submission_not_in_draftРабота уже не в статусе черновика — повторная отправка запрещена.
POST/api/v1/t/submissions/:id/review

Проверить работу (Принят / Не принят)

AdminCurator JWT право: manage_students

Куратор/админ выносит решение проверки. decision обязателен: accepted (Принят) или rejected (Не принят). score и feedback необязательны — допускается решение без балла. Непривилегированный куратор может проверять только своих студентов. Можно перепроверять уже проверенную работу (submitted/accepted/rejected → новое решение).

Решение НЕ влияет на прохождение курса: блок задания завершается при ОТПРАВКЕ работы (см. POST .../submit), а не при проверке. rejected НЕ откатывает завершение блока — оно лишь открывает пересдачу (следующий POST .../submissions создаёт новую попытку). На каждое решение (и accepted, и rejected) студенту отправляется уведомление; при наличии балла начисляются геймификация-очки. Легаси-статус graded больше не выставляется.

Тело запроса

ПараметрТипОписание
decisionобяз.stringРешение проверки: accepted | rejected.
scoreопц.stringБалл как числовая строка (в пределах max_score). Необязателен.
feedbackопц.stringHTML-отзыв (очищается сервером).

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

cURL
curl -X POST https://api.edumentor.kz/api/v1/t/submissions/5b5b.../review \
  -H "Authorization: Bearer <token>" \
  -H "X-Tenant-Subdomain: acme" \
  -H "Content-Type: application/json" \
  -d '{ "decision": "accepted", "score": "85", "feedback": "<p>Хорошая работа</p>" }'

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

200 OK · application/json
{
  "success": true,
  "data": {
    "id": "5b5b...",
    "assignment_id": "3c3c...",
    "student_id": "8f3b...",
    "status": "accepted",
    "score": "85",
    "feedback": "<p>Хорошая работа</p>",
    "graded_by_id": "c1c1...",
    "graded_at": "2026-05-28T12:00:00Z",
    "submitted_at": "2026-05-28T11:00:00Z",
    "attachments": [
      {
        "id": "att1...",
        "media_asset_id": "m2m2...",
        "position": 0,
        "filename": "essay.pdf",
        "presigned_url": "https://s3.edumentor.kz/media/...&X-Amz-Signature=...",
        "expires_in": 900
      }
    ],
    "created_at": "2026-05-28T10:00:00Z",
    "updated_at": "2026-05-28T12:00:00Z"
  }
}

Ошибки

КодerrorКогда
400invalid_id_formatID в пути не является UUID.
400invalid_request_bodyНекорректное тело запроса (например, decision не из accepted|rejected).
401unauthorizedШкола не определена или токен недействителен.
403permission_deniedНет права manage_students.
404submission_not_foundРабота отсутствует, другая школа или вне охвата куратора.
422review_decision_requireddecision не задан или не из accepted|rejected.
422submission_not_in_gradable_stateРабота не в состоянии, допускающем проверку (например, черновик).
422score_out_of_rangeБалл вне диапазона 0..max_score задания.
POST/api/v1/t/submissions/:id/reopen

Переоткрыть работу

AdminCurator JWT право: manage_students

Куратор/админ возвращает проверенную работу (accepted | rejected) в статус «отправлена» для повторной проверки. Балл и комментарий сохраняются; завершение блока не меняется. Тело пустое. Идемпотентно: уже отправленная работа — 200 без изменений; черновик переоткрыть нельзя (422).

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

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

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

cURL
curl -X POST https://api.edumentor.kz/api/v1/t/submissions/5b5b.../reopen \
  -H "Authorization: Bearer <token>" \
  -H "X-Tenant-Subdomain: acme"

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

200 OK · application/json
{
  "success": true,
  "data": {
    "id": "5b5b...",
    "assignment_id": "3c3c...",
    "student_id": "8f3b...",
    "status": "submitted",
    "score": null,
    "feedback": null,
    "graded_by_id": null,
    "graded_at": null,
    "submitted_at": "2026-05-28T11:00:00Z",
    "attachments": [
      {
        "id": "att1...",
        "media_asset_id": "m2m2...",
        "position": 0,
        "filename": "essay.pdf",
        "presigned_url": "https://s3.edumentor.kz/media/...&X-Amz-Signature=...",
        "expires_in": 900
      }
    ],
    "created_at": "2026-05-28T10:00:00Z",
    "updated_at": "2026-05-28T12:30:00Z"
  }
}

Ошибки

КодerrorКогда
400invalid_id_formatID в пути не является UUID.
401unauthorizedШкола не определена или токен недействителен.
403permission_deniedНет права manage_students.
404submission_not_foundРабота отсутствует, другая школа или вне охвата куратора.
422cannot_reopen_draftРабота в статусе черновика — переоткрытие невозможно.