# Lorachi API

> Обновлено: 2026-10-20. База: https://beta.lorachi.xyz.

Lorachi — юри-портал переводов: новеллы, манга, аниме, игры и сообщество.
API отдаёт JSON и не требует парсинга HTML. Для нового клиента выбирайте
/api/v1: это версионированный контракт с Bearer-токенами. /api/* —
API самого сайта: он использует cookie-сессию и CSRF-токен для мутаций.
Машинночитаемый контракт /api/v1 — OpenAPI 3.1: /api-docs/openapi.json.

## Быстрый старт

```bash
# Публичный каталог
curl "https://beta.lorachi.xyz/api/catalog/novels?page=1&limit=20"

# Мобильный API: вход и запрос с Bearer
curl -X POST "https://beta.lorachi.xyz/api/v1/auth/login" \
  -H "Content-Type: application/json" \
  -d '{"login":"username","password":"password"}'
curl "https://beta.lorachi.xyz/api/v1/home" -H "Authorization: Bearer $TOKEN"
```

## Авторизация

- **Сайт (`/api/*`)**: cookie-сессия. Для `POST`, `PUT`, `PATCH`, `DELETE` нужен CSRF-токен: cookie `XSRF-TOKEN` дублируется заголовком `x-xsrf-token`.
- **Приложение (`/api/v1/*`)**: `Authorization: Bearer <accessToken>`. Cookie и CSRF не используются. Access — 30 минут, refresh — 90 дней и ротируется.
- **OpenAPI 3.1**: https://beta.lorachi.xyz/api-docs/openapi.json — пути, схемы и авторизация /api/v1 для генераторов клиента.
- **Гость**: публичные GET-ручки доступны без входа. Для гостевого комментария или короткой ссылки всё равно нужен CSRF-токен гостевой сессии.

## Формат ответа

```json
{
  "error": {
    "code": "E_VALIDATION_ERROR",
    "message": "Проверьте поля формы",
    "details": {}
  }
}
```

Коды: `E_VALIDATION_ERROR` (422), `E_UNAUTHORIZED` (401), `E_FORBIDDEN` (403), `E_NOT_FOUND` (404), `E_PAID_CHAPTER` (403), `E_TOO_MANY_REQUESTS` (429, с `Retry-After`).

Пагинация: `page` с 1, `limit` по умолчанию 20; в ответе `meta: { page, limit, total, hasMore }`.

Лимиты: вход — 5 запросов / 15 минут; контентные действия (комментарии, арт, сообщения, загрузки) — 10 / минуту; короткие ссылки — 20 / 10 минут; для остальных ручек действуют свои лимиты. При превышении — `429` и `Retry-After`.

## Эндпоинты

### Авторизация и аккаунт

Вход на сайте живёт на cookie-сессии. Любой POST/PUT/PATCH/DELETE под /api/* требует CSRF-токен: cookie XSRF-TOKEN нужно продублировать заголовком x-xsrf-token. Нативному клиенту проще использовать /api/v1/auth/* и Bearer.

| Метод | Путь | Доступ | Описание |
|---|---|---|---|
| GET | `/health` | без входа | Живость приложения и состояние Mongo, R2 и hCaptcha. |
| POST | `/api/auth/register` | без входа + CSRF | Регистрация в web-сессии. Тело: username, email, password, captchaToken?. |
| POST | `/api/auth/login` | без входа + CSRF | Вход в web-сессии. Тело: login (username или email), password. |
| POST | `/api/auth/logout` | cookie + CSRF | Выход из текущей cookie-сессии. |
| POST | `/api/auth/logout-all` | cookie + CSRF | Выход из всех сессий пользователя. |
| GET | `/api/auth/me` | cookie-сессия | Текущий пользователь, подписка и счётчик уведомлений. |
| GET | `/api/auth/sessions` | cookie-сессия | Активные web-сеансы и сеансы приложения. |
| DELETE | `/api/auth/sessions/:id` | cookie + CSRF | Завершить сеанс web:<id> или mobile:<id>. |
| POST | `/api/auth/change-password` | cookie + CSRF | Сменить пароль и отозвать остальные сессии. |
| POST | `/api/auth/username` | cookie + CSRF | Сменить username. |
| POST | `/api/auth/display-name` | cookie + CSRF | Сменить отображаемое имя. |
| POST | `/api/auth/username-style` | cookie + CSRF | Настроить стиль ника. |
| POST | `/api/auth/privacy` | cookie + CSRF | Приватность профиля и сообщений. |
| POST | `/api/auth/bio` | cookie + CSRF | Обновить описание профиля. |
| POST | `/api/auth/profile-customization` | cookie + CSRF | Статус, обращение и виджеты профиля. |
| POST | `/api/auth/avatar` | cookie + CSRF | Загрузить аватар (multipart). |
| DELETE | `/api/auth/avatar` | cookie + CSRF | Удалить аватар. |
| POST | `/api/auth/banner` | cookie + CSRF | Загрузить баннер профиля (multipart). |
| DELETE | `/api/auth/banner` | cookie + CSRF | Удалить баннер. |
| POST | `/api/auth/banner-nsfw` | cookie + CSRF | Признак 18+ у баннера. |
| POST | `/api/auth/banner-gradient` | cookie + CSRF | Градиент баннера. |
| DELETE | `/api/auth/delete` | cookie + CSRF | Удалить аккаунт. |
| GET | `/api/auth/links` | cookie-сессия | Привязанные соцсети: id, label, linked, username, configured. |
| GET | `/api/auth/:provider/redirect` | без входа | Вход через соцсеть: discord, google, twitch (OAuth 2.0) или telegram (OIDC + PKCE). Ведёт на страницу провайдера. Параметр redirect — свой путь, куда вернуть после входа. |
| GET | `/api/auth/:provider/callback` | без входа | Callback провайдера: логин/регистрация или привязка. Браузер возвращается на сайт, Accept: application/json получает JSON. State одноразовый и лежит в сессии; у Telegram дополнительно проверяются PKCE, подпись id_token и nonce. |
| POST | `/api/auth/:provider/unlink` | cookie + CSRF | Отвязать OAuth-провайдера. |
| POST | `/api/auth/telegram/generate-link-code` | cookie + CSRF | Код привязки Telegram через бота (6 цифр, 10 минут). Ответ: code, expiresAt, botUrl, deepLink. Это совместимость со старым флоу; основной вход — OIDC. |
| GET | `/api/auth/telegram/check-link-status` | cookie-сессия | Статус привязки Telegram. |

### Мобильный API v1

Стабильный контракт для нативного клиента. База: /api/v1. Авторизация — Authorization: Bearer <accessToken>; cookie и CSRF не используются. Access живёт 30 минут, refresh — 90 дней и ротируется при каждом обмене.

| Метод | Путь | Доступ | Описание |
|---|---|---|---|
| POST | `/api/v1/auth/register` | без входа | Регистрация, сразу возвращает пару токенов. Тело: username, email, password, captchaToken?, deviceId?. |
| POST | `/api/v1/auth/login` | без входа | Вход по username/email и паролю. Тело: login, password, deviceId?. |
| POST | `/api/v1/auth/refresh` | без входа | Обмен refresh-токена на новую пару. Тело: refreshToken, deviceId?. |
| POST | `/api/v1/auth/logout` | Bearer | Выход только с этого устройства. |
| POST | `/api/v1/auth/logout-all` | Bearer | Выход со всех устройств. |
| GET | `/api/v1/auth/me` | Bearer | Пользователь, подписка и unread. |
| POST | `/api/v1/auth/link/start` | без входа | Начать вход по ссылке через сайт (Lite-OAuth). |
| POST | `/api/v1/auth/link/poll` | без входа | Опрос статуса link-входа; токены выдаются один раз. |
| GET | `/api/v1/auth/oauth/:provider/start` | без входа | Native OAuth/PKCE: 302 в Custom Tabs к Google или Discord. query: redirect_uri, code_challenge, code_challenge_method=S256, state, deviceId?. |
| GET | `/api/v1/auth/oauth/:provider/callback` | без входа | Callback провайдера: одноразовый код в lorachi://oauth, без токенов в URL. |
| POST | `/api/v1/auth/oauth/:provider/token` | без входа | Обмен одноразового кода и PKCE verifier на Bearer-пару. Тело: code, codeVerifier, deviceId?. Повторный обмен — 410. |
| GET | `/api/v1/me` | Bearer | Алиас пользователя, подписки и unread. |
| GET | `/api/v1/home` | гость или вход | Главная: featured, updates, continueReading. С Bearer персонализируется. |
| GET | `/api/v1/catalog` | гость или вход | Каталог новелл, манги и аниме. Фильтры: type, page, limit, genre, status, sort, order, search, featured. |
| GET | `/api/v1/search` | гость или вход | Поиск по тайтлам, авторам и пользователям. q — 2–100 символов. |
| GET | `/api/v1/novels/:id` | гость или вход | Карточка тайтла по ObjectId, alt или slug. |
| GET | `/api/v1/novels/:id/chapters` | гость или вход | Список глав; limit до 100. |
| GET | `/api/v1/novels/:id/content` | гость или вход | Том целиком для офлайна: markdown-текст или страницы манги. query: volume (по умолчанию 1). Платные главы без доступа — locked: true; том больше 4 МиБ — 413 E_VOLUME_TOO_LARGE. |
| GET | `/api/v1/chapters/:id` | гость или вход | Текстовая глава с prev/next. Платная глава без доступа: 403 E_PAID_CHAPTER. |
| GET | `/api/v1/manga/:novelId/chapters/:chapterId/pages` | гость или вход | Страницы манги в правильном порядке. |
| GET | `/api/v1/users/:username` | гость или вход | Публичный профиль без email. |
| PATCH | `/api/v1/me/profile` | Bearer | Частичная правка своего профиля. Тело: displayName, bio, status, pronouns, messagePrivacy, isPrivate — все необязательные. |
| POST | `/api/v1/users/:username/follow` | Bearer | Подписаться (идемпотентно). |
| DELETE | `/api/v1/users/:username/follow` | Bearer | Отписаться (идемпотентно). |
| GET | `/api/v1/users/:username/followers` | гость или вход | Подписчики, до 200. |
| GET | `/api/v1/users/:username/following` | гость или вход | Подписки, до 200. |
| GET | `/api/v1/users/:username/friends` | гость или вход | Взаимные подписки, до 200. |
| GET | `/api/v1/users/search` | гость или вход | Поиск людей по username и нику. query: q (от 2 символов), page, limit до 50. |
| GET | `/api/v1/users/online` | гость или вход | Кто активен последние 5 минут. |
| POST | `/api/v1/activity/update` | Bearer | Пульс присутствия для «онлайн» и карточки профиля. |
| GET | `/api/v1/comments` | гость или вход | Комментарии с деревом ответов и liked зрителя. query: targetType, targetId, sort, page, limit. |
| POST | `/api/v1/comments` | Bearer | Оставить комментарий или ответ (только аккаунт). Тело: targetType, targetId, content, parentId?, isAnonymous?. |
| POST | `/api/v1/comments/:id/like` | Bearer | Лайк/анлайк комментария. |
| DELETE | `/api/v1/comments/:id` | Bearer | Удалить свой комментарий. |
| GET | `/api/v1/anime` | гость или вход | Каталог аниме с фильтрами и пагинацией. query: page, limit, genre, type, status, sort, order, search. |
| GET | `/api/v1/anime/:idOrSlug` | гость или вход | Тайтл, серии по сезонам и состояние библиотеки. |
| GET | `/api/v1/anime/episodes/:id/streams` | без входа | Источники для плеера: треки, качества, встраиваемые плееры. private, no-store; скрытая серия — 404. |
| GET | `/api/v1/anime/episodes/:id/subtitles/:player/:subtitle` | без входа | WebVTT-субтитры через наш прокси. |
| POST | `/api/v1/devices` | Bearer | Зарегистрировать FCM-токен устройства. Тело: token, platform?, deviceId?. |
| DELETE | `/api/v1/devices/:token` | Bearer | Удалить FCM-токен устройства. |
| GET | `/api/v1/sessions` | Bearer | Активные сеансы: текущее устройство и сайт. |
| DELETE | `/api/v1/sessions/:id` | Bearer | Завершить выбранный сеанс. |
| GET | `/api/v1/library` | Bearer | Библиотека пользователя и сводка. |
| POST | `/api/v1/library/bulk` | Bearer | Массовая смена статуса или удаление. |
| PUT | `/api/v1/library/:kind/:id` | Bearer | Добавить или обновить запись. kind: novel, manga, anime. |
| DELETE | `/api/v1/library/:kind/:id` | Bearer | Убрать запись из библиотеки. |
| GET | `/api/v1/progress` | Bearer | Последние прочитанные тайтлы. |
| PUT | `/api/v1/progress` | Bearer | Сохранить позицию чтения. |
| POST | `/api/v1/progress/sync` | Bearer | Слить офлайн-прогресс, last-write-wins. |
| GET | `/api/v1/notifications` | Bearer | Уведомления и счётчики по категориям. |
| GET | `/api/v1/notifications/unread` | Bearer | Только счётчик непрочитанных. |
| POST | `/api/v1/notifications/read` | Bearer | Отметить выбранные или все уведомления. |
| POST | `/api/v1/notifications/clear-read` | Bearer | Удалить прочитанные. |
| GET | `/api/v1/notifications/settings` | Bearer | Настройки уведомлений по категориям. kinds: chapters, anime, social, rewards, games, system. Выключенная категория гасит и уведомления, и push. |
| PUT | `/api/v1/notifications/settings` | Bearer | Частично изменить категории уведомлений. Тело: kinds — объект «категория → true/false». Меняются только присланные ключи; неизвестная категория — 422. |
| POST | `/api/v1/notifications/:id/toggle` | Bearer | Переключить прочитано/непрочитано. |
| DELETE | `/api/v1/notifications/:id` | Bearer | Удалить уведомление. |

### Каталог, поиск и тайтлы

Публичное чтение каталога. Ответы каталога кэшируются примерно на минуту; персональные поля появляются только с валидной сессией.

| Метод | Путь | Доступ | Описание |
|---|---|---|---|
| GET | `/api/search` | без входа | Глобальный поиск: novels, authors, users. q короче 2 символов даёт пустой ответ. |
| GET | `/api/catalog/novels` | без входа | Каталог новелл. page, limit, genre, status, sort, order, search, featured. |
| GET | `/api/catalog/manga` | без входа | Каталог манги. Те же фильтры, что у новелл. |
| GET | `/api/catalog/updates` | без входа | Лента обновлений. Те же фильтры, что у каталога. |
| GET | `/api/catalog/recent-chapters` | без входа | Последние главы и серии. limit до 50; type, period. |
| GET | `/api/novels/:id` | гость или вход | Карточка тайтла по ObjectId, alt или slug. |
| GET | `/api/novels/:id/chapters` | гость или вход | Список глав тайтла. |
| GET | `/api/novels/:id/feed` | без входа | Лента обновлений тайтла. |
| GET | `/api/novels/:id/reviews` | без входа | Рецензии тайтла. |
| POST | `/api/novels/:id/rate` | cookie + CSRF | Поставить оценку тайтлу. |
| POST | `/api/novels/:id/reviews` | cookie + CSRF | Создать рецензию. |
| PUT | `/api/reviews/:id` | cookie + CSRF | Обновить свою рецензию. |
| DELETE | `/api/reviews/:id` | cookie + CSRF | Удалить свою рецензию. |
| POST | `/api/reviews/:id/like` | cookie + CSRF | Поставить или снять лайк рецензии. |
| GET | `/api/illustrations/:novelId` | без входа | Иллюстрации тайтла. |

### Чтение, прогресс и личная библиотека

Ридер, прогресс, заметки, закладки и покупки. Все личные ручки требуют cookie-сессию; мутации — ещё и CSRF. Номер главы в JSON всегда строка: в базе он Mixed (12, 12.5, prologue).

| Метод | Путь | Доступ | Описание |
|---|---|---|---|
| GET | `/api/chapters/:id` | гость или вход | Текстовая глава с навигацией prev/next. |
| POST | `/api/chapters/:id/like` | cookie + CSRF | Лайк главы. |
| DELETE | `/api/chapters/:id/like` | cookie + CSRF | Снять лайк главы. |
| PUT | `/api/chapters/:id/react` | cookie + CSRF | Поставить реакцию на главу. |
| DELETE | `/api/chapters/:id/react` | cookie + CSRF | Снять реакцию. |
| GET | `/api/manga/:novelId/chapters/:chapterId/pages` | гость или вход | Страницы манги по порядку. |
| POST | `/api/reader/heartbeat/start` | без входа + CSRF | Начать сессию чтения. |
| POST | `/api/reader/heartbeat` | без входа + CSRF | Продлить сессию чтения. |
| POST | `/api/reader/heartbeat/stop` | cookie + CSRF | Завершить сессию чтения. |
| GET | `/api/reading/progress` | cookie-сессия | Последние прочитанные тайтлы. |
| PUT | `/api/reading/progress` | cookie + CSRF | Сохранить позицию: novelId, chapter, volume?, percent?. |
| POST | `/api/reading/progress/sync` | cookie + CSRF | Слить офлайн-прогресс. |
| GET | `/api/reading/notes` | cookie-сессия | Заметки и выделения пользователя. |
| POST | `/api/reading/notes` | cookie + CSRF | Создать заметку или выделение. |
| PATCH | `/api/reading/notes/:id` | cookie + CSRF | Обновить заметку. |
| DELETE | `/api/reading/notes/:id` | cookie + CSRF | Удалить заметку. |
| GET | `/api/library` | cookie-сессия | Библиотека: новеллы, манга и аниме. |
| GET | `/api/library/export` | cookie-сессия | Экспорт библиотеки в CSV или JSON. |
| POST | `/api/library/bulk` | cookie + CSRF | Массовая смена статуса или удаление. |
| PUT | `/api/library/:kind/:id` | cookie + CSRF | Добавить или обновить запись. kind: novel, manga, anime. |
| DELETE | `/api/library/:kind/:id` | cookie + CSRF | Убрать запись. |
| POST | `/api/bookmarks` | cookie + CSRF | Добавить закладку на главу. |
| DELETE | `/api/bookmarks/:chapterId` | cookie + CSRF | Удалить закладку. |
| GET | `/api/bookmarks` | cookie-сессия | Список закладок. |
| POST | `/api/purchases/chapter` | cookie + CSRF | Купить платную главу. |
| GET | `/api/purchases` | cookie-сессия | История покупок. |

### Уведомления

Личные уведомления. Ответы приватные: Cache-Control: private, no-store. Счётчики и списки одинаковы для сайта и приложения, отличаются только способом входа.

| Метод | Путь | Доступ | Описание |
|---|---|---|---|
| GET | `/api/notifications` | cookie-сессия | Список уведомлений и счётчики по категориям. |
| GET | `/api/notifications/unread` | cookie-сессия | Счётчик непрочитанных. |
| POST | `/api/notifications/read` | cookie + CSRF | Отметить выбранные ids или все. |
| POST | `/api/notifications/clear-read` | cookie + CSRF | Удалить прочитанные. |
| POST | `/api/notifications/:id/toggle` | cookie + CSRF | Переключить прочитано/непрочитано. |
| DELETE | `/api/notifications/:id` | cookie + CSRF | Удалить уведомление. |

### Пользователи, стена и комментарии

Социальный слой сайта. Комментарии и реакции доступны только с сессией, кроме чтения и гостевого создания комментария (капча/лимит).

| Метод | Путь | Доступ | Описание |
|---|---|---|---|
| GET | `/api/users/online` | без входа | Кто сейчас на сайте. |
| GET | `/api/users/search` | без входа | Поиск пользователей. |
| GET | `/api/users/following` | cookie-сессия | На кого подписан текущий пользователь. |
| GET | `/api/users/:username` | без входа | Публичный профиль. |
| GET | `/api/users/:username/friends` | без входа | Друзья пользователя. |
| GET | `/api/users/:username/followers` | без входа | Подписчики. |
| GET | `/api/users/:username/following` | без входа | Подписки. |
| POST | `/api/users/:username/follow` | cookie + CSRF | Подписаться или отписаться. |
| POST | `/api/activity/update` | без входа + CSRF | Heartbeat «онлайн» для гостя или пользователя. |
| GET | `/api/online/widget` | без входа | HTML-виджет «Сейчас на сайте». |
| GET | `/api/comments` | без входа | Комментарии к тайтлу или главе. |
| POST | `/api/comments` | без входа + CSRF | Создать комментарий. Гостю нужна капча. |
| DELETE | `/api/comments/:id` | cookie + CSRF | Удалить свой комментарий. |
| POST | `/api/comments/:id/like` | cookie + CSRF | Поставить или снять лайк. |
| GET | `/api/chapters/:id/line-comments` | без входа | Комментарии к строке главы. |
| POST | `/api/chapters/:id/line-comments` | cookie + CSRF | Создать комментарий к строке. |
| POST | `/api/line-comments/:id/reply` | cookie + CSRF | Ответить на комментарий к строке. |
| POST | `/api/line-comments/:id/like` | cookie + CSRF | Лайк комментария к строке. |
| DELETE | `/api/line-comments/:id` | cookie + CSRF | Удалить комментарий к строке. |
| GET | `/api/sse/comments` | без входа | SSE-поток новых комментариев. |
| GET | `/api/art` | без входа | Посты стены/галереи. |
| POST | `/api/art` | cookie + CSRF | Создать пост. |
| GET | `/api/art/:id` | без входа | Один пост. |
| DELETE | `/api/art/:id` | cookie + CSRF | Удалить свой пост. |
| POST | `/api/art/:id/like` | cookie + CSRF | Лайк поста. |
| POST | `/api/art/:id/repost` | cookie + CSRF | Репост. |
| PUT | `/api/art/:id/comment-settings` | cookie + CSRF | Настройки комментариев поста. |
| POST | `/api/upload` | cookie + CSRF | Загрузка картинки в R2 (multipart). |

### Сообщения

Личные диалоги и групповые чаты. Все ручки требуют сессии; SSE-поток отдаёт события в реальном времени.

| Метод | Путь | Доступ | Описание |
|---|---|---|---|
| GET | `/api/messages/dialogs` | cookie-сессия | Список личных диалогов. |
| GET | `/api/messages/d/:userId` | cookie-сессия | История личного диалога. |
| POST | `/api/messages/d/:userId` | cookie + CSRF | Отправить личное сообщение. |
| POST | `/api/messages/read/:userId` | cookie + CSRF | Отметить диалог прочитанным. |
| GET | `/api/messages/group-chats` | cookie-сессия | Список групповых чатов. |
| POST | `/api/messages/group-chats` | cookie + CSRF | Создать групповой чат. |
| GET | `/api/messages/group-chats/:id/messages` | cookie-сессия | История группового чата. |
| POST | `/api/messages/group-chats/:id/messages` | cookie + CSRF | Отправить сообщение в чат. |
| POST | `/api/messages/group-chats/:id/join` | cookie + CSRF | Войти в чат. |
| POST | `/api/messages/group-chats/:id/leave` | cookie + CSRF | Выйти из чата. |
| POST | `/api/messages/group-messages/:id/vote` | cookie + CSRF | Проголосовать в опросе. |
| GET | `/api/sse/messages` | cookie-сессия | SSE-поток сообщений. |

### Вселенные и события

Публичные страницы вселенных, календаря и событий; подписка и награды — только с сессией.

| Метод | Путь | Доступ | Описание |
|---|---|---|---|
| GET | `/api/universes` | без входа | Список вселенных. |
| GET | `/api/universes/:slug` | без входа | Одна вселенная. |
| POST | `/api/universes/:slug/follow` | cookie + CSRF | Подписаться на вселенную. |
| POST | `/api/universes/:slug/claim-reward` | cookie + CSRF | Забрать награду вселенной. |
| GET | `/api/calendar` | без входа | Календарь релизов. |
| GET | `/api/events` | без входа | Список событий. |
| GET | `/api/events/:slug` | без входа | Одно событие. |

### Аниме

Плеер получает готовые ссылки на видео и проксирует субтитры. Ссылки на потоки приватные и не кэшируются: источник может требовать Referer.

| Метод | Путь | Доступ | Описание |
|---|---|---|---|
| GET | `/api/anime/episodes/:id/streams` | без входа | Плееры, качества и озвучки серии. Ответ не кэшируется. |
| GET | `/api/anime/subtitles/:id/:player/:subtitle` | без входа | Прокси субтитров в text/vtt. |

### Игры

Каталог переводов внешних игр, библиотека, заявки команд и модерация. Публичные карточки читаются без входа.

| Метод | Путь | Доступ | Описание |
|---|---|---|---|
| GET | `/api/games` | без входа | Каталог игр. |
| GET | `/api/games/:id` | без входа | Карточка игры. |
| GET | `/api/games/:id/reviews` | без входа | Рецензии игры. |
| POST | `/api/games/:id/reviews` | cookie + CSRF | Создать рецензию. |
| POST | `/api/games/:id/rate` | cookie + CSRF | Оценить игру. |
| POST | `/api/games/:id/download` | без входа + CSRF | Учесть скачивание. |
| POST | `/api/games/:id/report` | cookie + CSRF | Пожаловаться на игру. |
| PUT | `/api/games/:id/library` | cookie + CSRF | Добавить игру в библиотеку. |
| DELETE | `/api/games/:id/library` | cookie + CSRF | Убрать игру из библиотеки. |
| POST | `/api/games/:id/subscribe` | cookie + CSRF | Подписаться на обновления. |
| DELETE | `/api/games/:id/subscribe` | cookie + CSRF | Отписаться. |
| GET | `/api/games/library` | cookie-сессия | Библиотека игр пользователя. |
| GET | `/api/games/subscriptions` | cookie-сессия | Подписки на игры. |
| POST | `/api/games/applications` | cookie + CSRF | Заявка команды на перевод. |
| GET | `/api/games/applications/mine` | cookie-сессия | Мои заявки. |
| POST | `/api/games/:id/team-chat` | cookie + CSRF | Открыть чат команды игры. |

### Прогрессия, задания и аркада

XP, уровни, ежедневная награда, лидерборды, задания и внутренние мини-игры. Прогресс заданий пишут серверные хуки, а не клиент.

| Метод | Путь | Доступ | Описание |
|---|---|---|---|
| GET | `/api/progression` | cookie-сессия | XP, уровень и прогресс. |
| GET | `/api/progression/daily` | cookie-сессия | Статус ежедневной награды. |
| POST | `/api/progression/daily` | cookie + CSRF | Забрать ежедневную награду. |
| GET | `/api/leaderboard` | без входа | Лидерборд. |
| GET | `/api/quests` | cookie-сессия | Ежедневные, еженедельные и разовые задания. |
| GET | `/api/arcade/state` | гость или вход | Состояние аркады. |
| GET | `/api/arcade/leaderboard` | без входа | Рейтинг аркады. |
| POST | `/api/arcade/jumper/start` | cookie + CSRF | Начать забег в Jumpеr. |
| POST | `/api/arcade/jumper/finish` | cookie + CSRF | Завершить забег. |
| POST | `/api/arcade/platformer/start` | cookie + CSRF | Начать платформер. |
| POST | `/api/arcade/platformer/finish` | cookie + CSRF | Завершить платформер. |
| POST | `/api/arcade/roulette/spin` | cookie + CSRF | Крутить рулетку. |
| POST | `/api/arcade/roulette/tickets` | cookie + CSRF | Купить билеты рулетки. |
| POST | `/api/arcade/shop` | cookie + CSRF | Купить предмет аркады. |

### Гача, магазин и экономика

Монеты, предметы, подписка и коллекция персонажей. Публичные витрины читаются без входа, действия требуют сессии; баланс меняется только на сервере. Денежные POST принимают Idempotency-Key: повтор возвращает сохранённый ответ без второго списания.

| Метод | Путь | Доступ | Описание |
|---|---|---|---|
| GET | `/api/gacha/banners` | без входа | Баннеры гачи. |
| GET | `/api/gacha/pool` | без входа | Пул текущего баннера. |
| POST | `/api/gacha/pull` | cookie + CSRF | Сделать крутку. Idempotency-Key: повтор не тратит валюту второй раз. |
| POST | `/api/gacha/welcome-bonus` | cookie + CSRF | Забрать приветственный бонус. |
| GET | `/api/characters` | cookie-сессия | Коллекция персонажей. |
| GET | `/api/characters/:id` | cookie-сессия | Один персонаж. |
| POST | `/api/characters/:id/train` | cookie + CSRF | Тренировать персонажа. |
| POST | `/api/characters/:id/stats` | cookie + CSRF | Прокачать статы. |
| POST | `/api/characters/:id/interact` | cookie + CSRF | Взаимодействовать с персонажем. |
| GET | `/api/currency/balance` | cookie-сессия | Баланс монет. |
| GET | `/api/store` | без входа | Витрина магазина. |
| GET | `/api/subscription/status` | cookie-сессия | Статус подписки. |
| POST | `/api/store/buy` | cookie + CSRF | Купить предмет. Idempotency-Key: повтор вернёт ту же покупку, а не вторую. |
| POST | `/api/store/use` | cookie + CSRF | Использовать предмет. |
| POST | `/api/store/exchange` | cookie + CSRF | Обменять валюту. |
| GET | `/api/inventory` | cookie-сессия | Инвентарь. |
| POST | `/api/promo/redeem` | cookie + CSRF | Активировать промокод. |
| POST | `/api/subscription/buy` | cookie + CSRF | Купить или продлить подписку. |

### Сервисные и SEO-адреса

Не JSON-API, но полезно для интеграций и ИИ: короткие ссылки, RSS и карта сайта.

| Метод | Путь | Доступ | Описание |
|---|---|---|---|
| POST | `/api/shortlinks` | без входа + CSRF | Создать или получить короткую ссылку. Лимит 20 за 10 минут. |
| POST | `/api/feedback` | без входа + CSRF | Отправить обратную связь. Лимит 5 в час. |
| GET | `/robots.txt` | без входа | Правила краулеров. |
| GET | `/sitemap.xml` | без входа | Карта сайта. |
| GET | `/rss/chapters` | без входа | RSS последних глав. |
| GET | `/rss/novels` | без входа | RSS новых тайтлов. |
| GET | `/rss/wall` | без входа | RSS постов стены. |
| GET | `/s/:code` | без входа | Редирект короткой ссылки. |

### Staff и cron

Эти ручки не входят в публичный контракт и предназначены для админки и внешнего cron. Staff-доступ проверяет роль moderator/admin, cron — секрет X-Cron-Secret.

| Метод | Путь | Доступ | Описание |
|---|---|---|---|
| * | `/api/admin/games/*` | staff: moderator/admin | GET/POST/PUT/DELETE: игры, заявки, жалобы и загрузки. |
| * | `/api/admin/illustrations/*` | staff: moderator/admin | GET/POST/PUT/DELETE: иллюстрации тайтлов. |
| * | `/api/admin/import/jobs*` | staff: moderator/admin | GET/POST: импорт глав манги. |
| POST | `/api/bot/telegram/link` | X-Bot-Secret | Подтвердить код привязки Telegram — вызывает только @lorachi_notifier_bot. Заголовок X-Bot-Secret (TELEGRAM_BOT_SECRET или токен бота). Тело: code, telegramId, telegramUsername?, telegramFirstName?. |
| * | `/api/admin/anime/*` | staff: moderator/admin | GET/POST: импорт и скрытие аниме. |
| * | `/api/admin/title-sites/*` | staff: moderator/admin | GET/POST/PUT/DELETE: сабдомены тайтлов. |
| * | `/api/admin/users/*` | staff: moderator/admin | GET/POST: пользователи и бейджи; бан — только admin. |
| * | `/api/admin/economy/*` | staff: moderator/admin | GET/POST: экономика; ручная правка — только admin. |
| POST | `/api/cron/*` | X-Cron-Secret | Служебные cron-задачи. |

## Примеры DTO

### Ошибка

Все ошибки JSON-API имеют один конверт. Для 429 сервер также отдаёт Retry-After.

```json
{
  "error": {
    "code": "E_VALIDATION_ERROR",
    "message": "Проверьте поля формы",
    "details": {}
  }
}
```

### Пагинация

page начинается с 1, limit по умолчанию 20. hasMore позволяет не угадывать конец списка.

```json
{
  "data": [],
  "meta": {
    "page": 1,
    "limit": 20,
    "total": 120,
    "hasMore": true
  }
}
```

### Карточка тайтла

id — непрозрачная строка. number главы всегда строка, даже если выглядит числом.

```json
{
  "id": "66f1c2a1b2c3d4e5f6a7b8c9",
  "type": "novel",
  "title": "Название",
  "alt": "Альтернативное название",
  "coverUrl": "https://cdn.example/cover.webp",
  "status": "ongoing",
  "ageRating": "16+",
  "isAdult": false,
  "genres": ["фэнтези"],
  "tags": [],
  "chapters": 120,
  "views": 1000,
  "ratings": { "average": 4.7, "count": 52 },
  "url": "/novel/66f1c2a1b2c3d4e5f6a7b8c9"
}
```

### Глава

prev и next — готовые объекты главы или null; клиенту не нужно считать соседей самому.

```json
{
  "id": "66f1c2a1b2c3d4e5f6a7b8ca",
  "number": "12.5",
  "volume": 2,
  "title": "Название главы",
  "isPaid": false,
  "isEarlyAccess": false,
  "url": "/novel/66f1c2a1b2c3d4e5f6a7b8c9/volume/2/chapter/12.5",
  "publishedAt": "2026-10-19T00:00:00.000Z",
  "prev": null,
  "next": null
}
```

### Токены приложения

Access живёт 30 минут, refresh — 90 дней. Refresh ротируется; старый токен после обмена недействителен.

```json
{
  "accessToken": "lra_…",
  "refreshToken": "lrr_…",
  "tokenType": "Bearer",
  "expiresIn": 1800,
  "refreshExpiresIn": 7776000,
  "user": {}
}
```

### Сеанс

id — непрозрачная строка web:<id> или mobile:<family>. Клиент не должен разбирать её на части.

```json
{
  "id": "mobile:2f4c…",
  "kind": "mobile",
  "kindLabel": "Приложение",
  "deviceName": "Chrome · Android",
  "ip": "203.0.113.10",
  "createdAt": "2026-10-18T10:00:00.000Z",
  "lastSeenAt": "2026-10-19T09:00:00.000Z",
  "expiresAt": "2027-01-16T10:00:00.000Z",
  "current": true
}
```

## Для ИИ и автоматизации

- Предпочитайте JSON-ручки HTML-страницам: не нужно разбирать вёрстку.
- Для нового клиента используйте `/api/v1`: контракт версионирован и не зависит от cookie сайта.
- `id` считайте непрозрачной строкой; `number` главы — строкой, а не числом.
- Для списков используйте `page`/`limit` и `meta.hasMore`, не угадывайте конец по длине массива.
- Не вызывайте staff-ручки `/api/admin/*` без роли moderator/admin; они не входят в публичный контракт.

HTML-версия: https://beta.lorachi.xyz/api-docs

Конец документа.