Push-уведомления
Self-service настройка согласия и FCM-устройств, полный каталог автоматических событий, scoped options/preview аудитории и immutable push-кампании. Management endpoints доступны администратору или куратору с `can_manage_notifications`.
- GET
/api/v1/t/notifications/me/preferencesМоя настройка push - PATCH
/api/v1/t/notifications/me/preferencesВключить или выключить push - GET
/api/v1/t/notifications/meИстория моих уведомлений - GET
/api/v1/t/notifications/me/devicesМои push-устройства - POST
/api/v1/t/notifications/me/devicesСоздать или обновить устройство - PATCH
/api/v1/t/notifications/me/devices/:idОбновить устройство - DELETE
/api/v1/t/notifications/me/devices/:idУдалить устройство - GET
/api/v1/t/notifications/admin/eventsКаталог и настройки событий - PATCH
/api/v1/t/notifications/admin/events/:codeПереопределить событие - GET
/api/v1/t/notifications/admin/audience/optionsВарианты для конструктора аудитории - POST
/api/v1/t/notifications/admin/audience/previewПредпросмотр аудитории - POST
/api/v1/t/notifications/admin/campaignsСоздать push-кампанию - GET
/api/v1/t/notifications/admin/campaignsИстория кампаний - GET
/api/v1/t/notifications/admin/campaigns/:idДетали кампании - POST
/api/v1/t/notifications/admin/campaigns/:id/cancelОтменить кампанию
/api/v1/t/notifications/me/preferencesМоя настройка push
Возвращает общий push-выключатель текущего пользователя. Если сохранённой настройки ещё нет, API возвращает effective default true; строка создаётся при первом PATCH. Фактическая доставка всё равно требует browser permission и активного устройства.
Пример запроса
curl https://api.edumentor.kz/api/v1/t/notifications/me/preferences \
-H "Authorization: Bearer <token>" \
-H "X-Tenant-Subdomain: acme"Пример ответа
{
"success": true,
"data": {
"preference": {
"push_enabled": true,
"updated_at": "2026-07-21T05:00:00Z"
}
}
}Ошибки
| Код | error | Когда |
|---|---|---|
| 401 | unauthorized | JWT отсутствует, истёк или недействителен. |
| 403 | forbidden | Нет tenant-доступа или права can_manage_notifications. |
/api/v1/t/notifications/me/preferencesВключить или выключить push
Атомарно меняет общий push-выключатель аккаунта. Выключение suppress-ит новые доставки, но не удаляет устройства; их можно включать, выключать и удалять отдельно.
Тело запроса
| Параметр | Тип | Описание |
|---|---|---|
push_enabledобяз. | boolean | Разрешена ли push-доставка для аккаунта. |
Пример запроса
curl -X PATCH https://api.edumentor.kz/api/v1/t/notifications/me/preferences \
-H "Authorization: Bearer <token>" \
-H "X-Tenant-Subdomain: acme" \
-H "Content-Type: application/json" \
-d '{"push_enabled":false}'Пример ответа
{"success":true,"data":{"preference":{"push_enabled":false,"updated_at":"2026-07-21T05:10:00Z"}}}Ошибки
| Код | error | Когда |
|---|---|---|
| 401 | unauthorized | JWT отсутствует, истёк или недействителен. |
| 403 | forbidden | Нет tenant-доступа или права can_manage_notifications. |
| 400 | push_enabled is required | Поле отсутствует или имеет не boolean-тип. |
/api/v1/t/notifications/meИстория моих уведомлений
Возвращает пагинированную историю успешно доставленных push текущего пользователя в текущей школе. Одна логическая запись возвращается один раз, даже если push был отправлен на несколько устройств. Отключённые, подавленные и окончательно не доставленные записи в историю не попадают.
Query-параметры
| Параметр | Тип | Описание |
|---|---|---|
pageопц. | integer | Страница от 1; по умолчанию 1. |
limitопц. | integer | 1…100; по умолчанию 20. |
Пример запроса
curl "https://api.edumentor.kz/api/v1/t/notifications/me?page=1&limit=20" \
-H "Authorization: Bearer <token>" \
-H "X-Tenant-Subdomain: acme"Пример ответа
{
"success": true,
"data": {
"notifications": [{
"id": "6d7e8f90-...",
"event_code": "assignment.accepted",
"category": "assignment",
"title": "Задание принято",
"body": "Работа «Лабораторная №1» принята. Оценка: 95.00.",
"deep_link": null,
"data": {"event_code":"assignment.accepted"},
"aggregate_type": "submission",
"aggregate_id": "3c4d5e6f-...",
"sent_at": "2026-07-21T06:30:00Z",
"created_at": "2026-07-21T06:29:55Z"
}],
"total": 1,
"page": 1,
"limit": 20
}
}Ошибки
| Код | error | Когда |
|---|---|---|
| 401 | unauthorized | JWT отсутствует, истёк или недействителен. |
| 403 | forbidden | Нет tenant-доступа или права can_manage_notifications. |
| 400 | invalid_pagination | page/limit не являются целыми числами или выходят за допустимый диапазон. |
/api/v1/t/notifications/me/devicesМои push-устройства
Возвращает только устройства текущего пользователя в текущей школе. Plaintext FCM token, hash и ciphertext никогда не сериализуются.
Пример ответа
{
"success": true,
"data": {
"devices": [{
"id": "2a2b3c4d-...",
"installation_id": "d5e6f7a8-...",
"provider": "fcm",
"platform": "web",
"enabled": true,
"app_version": "2026.07.21",
"last_seen_at": "2026-07-21T05:00:00Z",
"created_at": "2026-07-20T11:00:00Z",
"updated_at": "2026-07-21T05:00:00Z"
}]
}
}Ошибки
| Код | error | Когда |
|---|---|---|
| 401 | unauthorized | JWT отсутствует, истёк или недействителен. |
| 403 | forbidden | Нет tenant-доступа или права can_manage_notifications. |
/api/v1/t/notifications/me/devicesСоздать или обновить устройство
Идемпотентный upsert по (tenant, user, provider, installation_id). Повторный вызов обновляет ротированный token, platform и app_version и включает устройство. Если такой token уже принадлежал другой установке, старый binding инвалидируется безопасно.
Тело запроса
| Параметр | Тип | Описание |
|---|---|---|
installation_idобяз. | string | Стабильный ID установки, 1…255 символов. |
tokenобяз. | string | Write-only FCM registration token, 1…4096 символов. |
platformобяз. | string | web, ios или android. |
providerопц. | string | Только fcm; по умолчанию fcm. |
app_versionопц. | string|null | Версия приложения, максимум 64 символа. |
Пример запроса
curl -X POST https://api.edumentor.kz/api/v1/t/notifications/me/devices \
-H "Authorization: Bearer <token>" \
-H "X-Tenant-Subdomain: acme" \
-H "Content-Type: application/json" \
-d '{"installation_id":"d5e6f7a8-...","token":"<fcm-token>","provider":"fcm","platform":"web"}'Пример ответа
{"success":true,"data":{"device":{"id":"2a2b3c4d-...","installation_id":"d5e6f7a8-...","provider":"fcm","platform":"web","enabled":true,"app_version":null,"last_seen_at":"2026-07-21T05:00:00Z","created_at":"2026-07-21T05:00:00Z","updated_at":"2026-07-21T05:00:00Z"}}}Ошибки
| Код | error | Когда |
|---|---|---|
| 401 | unauthorized | JWT отсутствует, истёк или недействителен. |
| 403 | forbidden | Нет tenant-доступа или права can_manage_notifications. |
| 400 | invalid_request_body | Невалидный installation_id, token, provider или platform. |
| 503 | push_not_configured | Не задан PUSH_TOKEN_KEY_FILE с доступным 32-byte ключом. |
/api/v1/t/notifications/me/devices/:idОбновить устройство
Частично обновляет принадлежащее текущему пользователю устройство. installation_id и provider неизменяемы. Token остаётся write-only.
Параметры пути
| Параметр | Тип | Описание |
|---|---|---|
idобяз. | uuid | ID устройства. |
Тело запроса
| Параметр | Тип | Описание |
|---|---|---|
tokenопц. | string | Новый FCM token при ротации. |
platformопц. | string | web, ios или android. |
enabledопц. | boolean | Активно ли это устройство. |
app_versionопц. | string|null | Версия приложения. |
Пример ответа
{"success":true,"data":{"device":{"id":"2a2b3c4d-...","installation_id":"d5e6f7a8-...","provider":"fcm","platform":"web","enabled":false}}}Ошибки
| Код | error | Когда |
|---|---|---|
| 401 | unauthorized | JWT отсутствует, истёк или недействителен. |
| 403 | forbidden | Нет tenant-доступа или права can_manage_notifications. |
| 404 | push_device_not_found | Устройство не найдено у текущего пользователя. |
/api/v1/t/notifications/me/devices/:idУдалить устройство
Мягко удаляет binding устройства текущего пользователя и необратимо очищает сохранённые ciphertext/hash токена. Клиент также должен вызвать Firebase deleteToken() для текущей установки.
Параметры пути
| Параметр | Тип | Описание |
|---|---|---|
idобяз. | uuid | ID устройства. |
Пример ответа
{"success":true,"data":{"deleted":true}}Ошибки
| Код | error | Когда |
|---|---|---|
| 401 | unauthorized | JWT отсутствует, истёк или недействителен. |
| 403 | forbidden | Нет tenant-доступа или права can_manage_notifications. |
| 404 | push_device_not_found | Устройство не найдено у текущего пользователя. |
/api/v1/t/notifications/admin/eventsКаталог и настройки событий
Возвращает все 63 hard-coded event definition вместе с effective tenant-настройкой. Для текущего каталога producer подключён у каждого события (implemented=true). Поле остаётся в контракте как защита для будущих событий, которые могут быть добавлены до подключения источника.
Пример ответа
{
"success": true,
"data": {
"events": [{
"code": "lesson.deadline_reminder",
"category": "schedule",
"name": "Напоминание о дедлайне",
"description": "Ежедневное напоминание за N дней до закрытия урока",
"default_enabled": true,
"default_config": {"days_before":3,"repeat_daily":true,"send_at_local":"09:00"},
"implemented": true,
"source": "deadline_scan",
"recipients": "students",
"variables": ["lesson_title","deadline","days_left"],
"enabled": true,
"config": {"days_before":3,"repeat_daily":true,"send_at_local":"09:00"},
"title_template": "Скоро дедлайн урока",
"body_template": "Завершите «{{lesson_title}}» до {{deadline}}.",
"overridden": false
}]
}
}Ошибки
| Код | error | Когда |
|---|---|---|
| 401 | unauthorized | JWT отсутствует, истёк или недействителен. |
| 403 | forbidden | Нет tenant-доступа или права can_manage_notifications. |
/api/v1/t/notifications/admin/events/:codeПереопределить событие
Upsert effective-настройки известного и реализованного event code. Код события создать через API нельзя. Шаблоны trim-ятся и валидируются в UTF-8 bytes.
Параметры пути
| Параметр | Тип | Описание |
|---|---|---|
codeобяз. | string | Код из server catalog. |
Тело запроса
| Параметр | Тип | Описание |
|---|---|---|
enabledопц. | boolean | Включено ли автоматическое событие. |
configопц. | object | Config известного события. Для lesson.deadline_reminder и assignment.deadline_reminder: days_before — целое 1…90, repeat_daily — boolean, send_at_local — HH:MM. Для progress.stalled: inactive_days — целое 1…365, send_at_local — HH:MM. Для billing.expiry_reminder: days_before — непустой массив максимум из 30 целых значений 1…365, send_at_local — HH:MM. |
title_templateопц. | string | Непустой шаблон, максимум 160 UTF-8 bytes. |
body_templateопц. | string | Непустой шаблон, максимум 2000 UTF-8 bytes. |
Пример запроса
curl -X PATCH https://api.edumentor.kz/api/v1/t/notifications/admin/events/lesson.deadline_reminder \
-H "Authorization: Bearer <token>" \
-H "X-Tenant-Subdomain: acme" \
-H "Content-Type: application/json" \
-d '{"enabled":true,"config":{"days_before":3,"repeat_daily":true,"send_at_local":"09:00"}}'Ошибки
| Код | error | Когда |
|---|---|---|
| 401 | unauthorized | JWT отсутствует, истёк или недействителен. |
| 403 | forbidden | Нет tenant-доступа или права can_manage_notifications. |
| 400 | notification_validation_failed | Невалидный config или превышен byte-limit шаблона. |
| 404 | notification_event_not_found | Код отсутствует в catalog. |
| 409 | notification_event_not_implemented | Producer события ещё не подключён. |
/api/v1/t/notifications/admin/audience/optionsВарианты для конструктора аудитории
Возвращает минимальные id/label/subtitle без зависимости от других curator permissions. Для куратора students ограничены его учениками, groups — назначенными группами, а остальные объекты — связанными с его student scope.
Query-параметры
| Параметр | Тип | Описание |
|---|---|---|
kindобяз. | string | student, group, course, module, lesson, quiz, assignment, exam или path. |
qопц. | string | Поиск по label/subtitle. |
limitопц. | integer | 1…50, по умолчанию 20. |
Пример ответа
{"success":true,"data":{"kind":"course","options":[{"id":"1a2b3c4d-...","label":"Математика ҰБТ","subtitle":"published"}],"total":1,"limit":20}}Ошибки
| Код | error | Когда |
|---|---|---|
| 401 | unauthorized | JWT отсутствует, истёк или недействителен. |
| 403 | forbidden | Нет tenant-доступа или права can_manage_notifications. |
| 400 | invalid_notification_audience | Неизвестный kind или невалидный limit. |
/api/v1/t/notifications/admin/audience/previewПредпросмотр аудитории
Считает активных учеников, соответствующих filter, и отдаёт небольшую PII-выборку. Ничего не сохраняет и не отправляет. Результат для куратора всегда пересекается с его student scope.
Пустой rules означает всех активных учеников tenant. Relation-поля поддерживают in/not_in; *_score_pct — values UUID[] + gte/lte value 0…100; points_total/rank — gte/lte; last_active_at — before/after RFC3339; push_enabled и has_device — eq со строкой true/false.
Тело запроса
| Параметр | Тип | Описание |
|---|---|---|
filterобяз. | object | {match:'all'|'any', rules:[{field,operator,values?|value?}]}. |
sample_limitопц. | integer | Размер sample 1…100. |
Пример запроса
curl -X POST https://api.edumentor.kz/api/v1/t/notifications/admin/audience/preview \
-H "Authorization: Bearer <token>" \
-H "X-Tenant-Subdomain: acme" \
-H "Content-Type: application/json" \
-d '{"filter":{"match":"all","rules":[{"field":"course_enrollment","operator":"in","values":["1a2b3c4d-..."]},{"field":"has_device","operator":"eq","value":"true"}]},"sample_limit":20}'Пример ответа
{"success":true,"data":{"audience":{"count":42,"sample":[{"id":"9a8b7c6d-...","email":"student@example.kz","first_name":"Алия","last_name":"С."}],"sample_limit":20,"capped":true}}}Ошибки
| Код | error | Когда |
|---|---|---|
| 401 | unauthorized | JWT отсутствует, истёк или недействителен. |
| 403 | forbidden | Нет tenant-доступа или права can_manage_notifications. |
| 400 | invalid_notification_audience | Неизвестное поле/оператор, пустые values, invalid UUID/RFC3339/boolean. |
/api/v1/t/notifications/admin/campaignsСоздать push-кампанию
Повторно вычисляет аудиторию, сохраняет immutable snapshot получателей и создаёт scheduled campaign. scheduled_at null/omitted означает NOW. Preview не является авторитетным: фактический размер находится в campaign.audience_count.
Тело запроса
| Параметр | Тип | Описание |
|---|---|---|
titleобяз. | string | Непустой, максимум 160 UTF-8 bytes. |
bodyобяз. | string | Непустой, максимум 2000 UTF-8 bytes. |
deep_linkопц. | string|null | Только безопасный same-origin relative path, максимум 1000 символов. |
filterобяз. | object | Audience filter как в preview. |
scheduled_atопц. | RFC3339|null | Будущее время; null = отправить сейчас. |
Пример запроса
curl -X POST https://api.edumentor.kz/api/v1/t/notifications/admin/campaigns \
-H "Authorization: Bearer <token>" \
-H "X-Tenant-Subdomain: acme" \
-H "Content-Type: application/json" \
-d '{"title":"Занятие перенесено","body":"Откройте новое расписание.","deep_link":"/courses","filter":{"match":"all","rules":[{"field":"group_id","operator":"in","values":["1a2b3c4d-..."]}]},"scheduled_at":null}'Пример ответа
{
"success": true,
"data": {"campaign": {
"id":"4a5b6c7d-...","status":"scheduled","title":"Занятие перенесено",
"audience_filter":{"match":"all","rules":[{"field":"group_id","operator":"in","values":["1a2b3c4d-..."]}]},
"audience_count":42,"outbox_created_count":0,"sent_count":0,"failed_count":0,
"scheduled_at":"2026-07-21T05:20:00Z","started_at":null,"completed_at":null,"cancelled_at":null
}}
}Ошибки
| Код | error | Когда |
|---|---|---|
| 401 | unauthorized | JWT отсутствует, истёк или недействителен. |
| 403 | forbidden | Нет tenant-доступа или права can_manage_notifications. |
| 400 | notification_validation_failed | Пустой/слишком большой текст, unsafe deep_link или past scheduled_at. |
| 400 | invalid_notification_audience | Невалидный filter. |
/api/v1/t/notifications/admin/campaignsИстория кампаний
Пагинированная история tenant. Куратор видит только созданные им кампании; администратор — все кампании школы.
Query-параметры
| Параметр | Тип | Описание |
|---|---|---|
pageопц. | integer | Страница от 1. |
limitопц. | integer | Размер страницы. |
Пример ответа
{"success":true,"data":{"campaigns":[{"id":"4a5b6c7d-...","status":"completed","title":"Занятие перенесено","audience_count":42,"outbox_created_count":42,"sent_count":40,"failed_count":2}],"total":1,"page":1,"limit":20}}Ошибки
| Код | error | Когда |
|---|---|---|
| 401 | unauthorized | JWT отсутствует, истёк или недействителен. |
| 403 | forbidden | Нет tenant-доступа или права can_manage_notifications. |
/api/v1/t/notifications/admin/campaigns/:idДетали кампании
Возвращает campaign со snapshot filter и delivery counters. Куратор может читать только созданную им кампанию.
Параметры пути
| Параметр | Тип | Описание |
|---|---|---|
idобяз. | uuid | ID кампании. |
Ошибки
| Код | error | Когда |
|---|---|---|
| 401 | unauthorized | JWT отсутствует, истёк или недействителен. |
| 403 | forbidden | Нет tenant-доступа или права can_manage_notifications. |
| 404 | notification_campaign_not_found | Кампания не найдена в доступном scope. |
/api/v1/t/notifications/admin/campaigns/:id/cancelОтменить кампанию
Переводит scheduled/processing кампанию в cancelled и suppress-ит ещё не отправленные записи. Уже доставленные push не отзываются. Куратор может отменить только свою кампанию.
Параметры пути
| Параметр | Тип | Описание |
|---|---|---|
idобяз. | uuid | ID кампании. |
Пример ответа
{"success":true,"data":{"campaign":{"id":"4a5b6c7d-...","status":"cancelled","cancelled_at":"2026-07-21T05:15:00Z"}}}Ошибки
| Код | error | Когда |
|---|---|---|
| 401 | unauthorized | JWT отсутствует, истёк или недействителен. |
| 403 | forbidden | Нет tenant-доступа или права can_manage_notifications. |
| 404 | notification_campaign_not_found | Кампания не найдена в доступном scope. |
| 409 | notification_campaign_immutable | Кампания уже completed/cancelled/failed и не может быть отменена. |