TenantAdminCuratorStudent

Задания

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

GET/api/v1/t/assignments

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

Tenant JWT

Постраничный список заданий школы. Доступен любому авторизованному пользователю школы. Списочные строки не гидрируют вложения (attachments остаётся пустым). Для роли 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",
        "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

Возвращает задание с гидрированным списком прикреплённых преподавателем файлов (имя, размер, тип, 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",
    "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.

201 Created.

Тело запроса

ПараметрТипОписание
titleобяз.stringНазвание задания (1..255).
instructionsопц.stringHTML-инструкции (очищаются сервером).
max_scoreопц.stringМаксимальный балл как числовая строка.Пример: 100

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

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" }'

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

200 OK · application/json
{
  "success": true,
  "data": {
    "id": "3c3c...",
    "title": "Эссе по главе 1",
    "instructions": "<p>Напишите эссе...</p>",
    "max_score": "100",
    "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

Частичное обновление задания. Переданные поля заменяют значения; отсутствующие остаются прежними.

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

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

Тело запроса

ПараметрТипОписание
titleопц.stringНовое название (1..255).
instructionsопц.stringНовые HTML-инструкции.
max_scoreопц.stringНовый максимальный балл как числовая строка.

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

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" }'

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

200 OK · application/json
{
  "success": true,
  "data": {
    "id": "3c3c...",
    "title": "Эссе по главе 1",
    "instructions": "<p>Напишите эссе...</p>",
    "max_score": "80",
    "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). Статусы 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...",
    "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Задание отсутствует или принадлежит другой школе.
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Работа в статусе черновика — переоткрытие невозможно.