# API Документация для Frontend ## Базовая информация - **Base URL**: `/api/v1` (для всех эндпоинтов) - **Формат данных**: JSON - **Аутентификация**: JWT Bearer Token (в заголовке `Authorization: Bearer `) ## Авторизация ### Процесс авторизации через Telegram 1. Пользователь запускает Telegram бота командой `/start` с параметром `login` 2. Бот создает одноразовый токен (живет 10 минут) и отправляет ссылку на веб-приложение 3. Фронтенд получает `token` из URL параметра (`/auth/complete?token=...`) 4. Фронтенд отправляет запрос на `GET /api/v1/auth/complete?token=` 5. Бэкенд возвращает JWT токен, который нужно сохранить и использовать для всех последующих запросов ### GET `/api/v1/auth/complete` Обмен одноразового токена на JWT access token. **Query параметры:** - `token` (string, обязательный) - одноразовый токен из Telegram бота **Response 200:** ```json { "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." } ``` **JWT токен содержит:** - `user_id` (UUID) - ID пользователя - `telegram_id` (integer) - Telegram ID пользователя - `username` (string | null) - Telegram username - `exp` - время истечения токена (7 дней по умолчанию) **Ошибки:** - `404` - токен не найден - `400` - токен уже использован или истек ### GET `/api/v1/auth/me` Получить информацию о текущем пользователе. **Headers:** - `Authorization: Bearer ` **Response 200:** ```json { "id": "550e8400-e29b-41d4-a716-446655440000", "telegram_id": 123456789, "username": "user_name" } ``` **Поля:** - `id` (UUID) - ID пользователя в системе - `telegram_id` (integer) - Telegram ID - `username` (string | null) - Telegram username (может быть null) --- ## Целевые каналы (Target Channels) Каналы пользователя, в которых установлен бот. ### GET `/api/v1/target_channels` Получить список всех целевых каналов пользователя. **Headers:** - `Authorization: Bearer ` **Response 200:** ```json { "target_channels": [ { "id": "uuid", "telegram_id": 123456789, "title": "Название канала", "username": "channel_username", "is_active": true } ] } ``` ### DELETE `/api/v1/target_channels/{channel_id}` Отключить целевой канал (удалить из системы). **Headers:** - `Authorization: Bearer ` **Path параметры:** - `channel_id` (UUID) - ID канала **Response 200:** пустой ответ --- ## Внешние каналы (External Channels) Каналы, в которых размещается реклама. ### GET `/api/v1/external_channels/target/{target_channel_id}` Получить все внешние каналы, связанные с конкретным целевым каналом. **Headers:** - `Authorization: Bearer ` **Path параметры:** - `target_channel_id` (UUID) - ID целевого канала **Response 200:** ```json { "external_channels": [ { "id": "uuid", "telegram_id": 123456789, "title": "Внешний канал", "username": "external_channel", "description": "Описание канала", "subscribers_count": 10000 } ] } ``` ### POST `/api/v1/external_channels` Создать новый внешний канал. **Headers:** - `Authorization: Bearer ` **Request body:** ```json { "telegram_id": 123456789, "title": "Название канала", "username": "channel_username", "description": "Описание", "subscribers_count": 10000, "target_channel_ids": ["uuid1", "uuid2"] } ``` **Поля:** - `telegram_id` (integer, обязательный) - Telegram ID канала - `title` (string, обязательный) - название канала - `username` (string, optional) - username канала (без @) - `description` (string, optional) - описание канала - `subscribers_count` (integer, optional) - количество подписчиков - `target_channel_ids` (array of UUID, обязательный) - список ID целевых каналов для связи **Response 200:** объект `ExternalChannelOutput` ### PATCH `/api/v1/external_channels/{channel_id}` Обновить информацию о внешнем канале. **Headers:** - `Authorization: Bearer ` **Path параметры:** - `channel_id` (UUID) - ID канала **Request body:** ```json { "title": "Новое название", "username": "new_username", "description": "Новое описание", "subscribers_count": 15000 } ``` **Все поля опциональны.** Отправляйте только те, которые нужно изменить. **Response 200:** объект `ExternalChannelOutput` ### PATCH `/api/v1/external_channels/{channel_id}/links` Обновить связи внешнего канала с целевыми каналами. **Headers:** - `Authorization: Bearer ` **Path параметры:** - `channel_id` (UUID) - ID канала **Request body:** ```json { "add_target_channel_ids": ["uuid1", "uuid2"], "remove_target_channel_ids": ["uuid3"] } ``` **Поля:** - `add_target_channel_ids` (array of UUID, optional, default=[]) - добавить связи - `remove_target_channel_ids` (array of UUID, optional, default=[]) - удалить связи **Response 200:** объект `ExternalChannelOutput` ### DELETE `/api/v1/external_channels/{channel_id}` Удалить внешний канал. **Headers:** - `Authorization: Bearer ` **Path параметры:** - `channel_id` (UUID) - ID канала **Response 200:** пустой ответ --- ## Креативы (Creatives) Рекламные сообщения для размещения. ### GET `/api/v1/creatives` Получить список креативов. **Headers:** - `Authorization: Bearer ` **Query параметры:** - `target_channel_id` (UUID, optional) - фильтр по целевому каналу - `include_archived` (boolean, optional, default=false) - включить архивные **Response 200:** ```json { "creatives": [ { "id": "uuid", "name": "Название креатива", "text": "Текст рекламного сообщения", "target_channel_id": "uuid", "target_channel_title": "Название канала", "created_at": "2024-01-01T12:00:00Z", "status": "active", "placements_count": 5 } ] } ``` **Статусы креатива:** - `active` - активный - `archived` - архивный ### GET `/api/v1/creatives/{creative_id}` Получить конкретный креатив. **Headers:** - `Authorization: Bearer ` **Path параметры:** - `creative_id` (UUID) - ID креатива **Response 200:** объект `CreativeOutput` ### POST `/api/v1/creatives` Создать новый креатив. **Headers:** - `Authorization: Bearer ` **Request body:** ```json { "name": "Название креатива", "text": "Текст рекламного сообщения", "target_channel_id": "uuid" } ``` **Поля:** - `name` (string, обязательный) - название креатива - `text` (string, обязательный) - текст сообщения - `target_channel_id` (UUID, обязательный) - ID целевого канала **Response 200:** объект `CreativeOutput` ### PATCH `/api/v1/creatives/{creative_id}` Обновить креатив. **Headers:** - `Authorization: Bearer ` **Path параметры:** - `creative_id` (UUID) - ID креатива **Request body:** ```json { "name": "Новое название", "text": "Новый текст", "status": "archived" } ``` **Все поля опциональны.** **Response 200:** объект `CreativeOutput` ### DELETE `/api/v1/creatives/{creative_id}` Удалить креатив. **Headers:** - `Authorization: Bearer ` **Path параметры:** - `creative_id` (UUID) - ID креатива **Response 200:** пустой ответ --- ## Размещения (Placements) Размещения рекламы во внешних каналах. ### GET `/api/v1/placements` Получить список размещений. **Headers:** - `Authorization: Bearer ` **Query параметры:** - `target_channel_id` (UUID, optional) - фильтр по целевому каналу - `external_channel_id` (UUID, optional) - фильтр по внешнему каналу - `creative_id` (UUID, optional) - фильтр по креативу - `include_archived` (boolean, optional, default=false) - включить архивные **Response 200:** ```json { "placements": [ { "id": "uuid", "target_channel_id": "uuid", "target_channel_title": "Целевой канал", "external_channel_id": "uuid", "external_channel_title": "Внешний канал", "creative_id": "uuid", "creative_name": "Название креатива", "placement_date": "2024-01-01T12:00:00Z", "cost": 1000.5, "comment": "Комментарий", "ad_post_url": "https://t.me/channel/123", "invite_link_type": "approval", "invite_link": "https://t.me/+unique_invite_link", "status": "active", "subscriptions_count": 42, "views_count": 1500, "views_availability": "available", "last_views_fetch_at": "2024-01-02T10:00:00Z", "created_at": "2024-01-01T12:00:00Z" } ] } ``` **Типы пригласительных ссылок (invite_link_type):** - `public` - обычная открытая ссылка - `approval` - ссылка с одобрением бота (отслеживание подписчиков) **Статусы размещения (status):** - `active` - активное - `archived` - архивное **Доступность просмотров (views_availability):** - `unknown` - ещё не проверялось - `available` - просмотры доступны через API - `unavailable` - нет доступа к просмотрам - `manual` - просмотры вводятся вручную ### GET `/api/v1/placements/{placement_id}` Получить конкретное размещение. **Headers:** - `Authorization: Bearer ` **Path параметры:** - `placement_id` (UUID) - ID размещения **Response 200:** объект `PlacementOutput` ### POST `/api/v1/placements` Создать новое размещение. **Headers:** - `Authorization: Bearer ` **Request body:** ```json { "target_channel_id": "uuid", "external_channel_id": "uuid", "creative_id": "uuid", "placement_date": "2024-01-01T12:00:00Z", "cost": 1000.5, "comment": "Комментарий", "ad_post_url": "https://t.me/channel/123", "invite_link_type": "approval" } ``` **Поля:** - `target_channel_id` (UUID, обязательный) - `external_channel_id` (UUID, обязательный) - `creative_id` (UUID, обязательный) - `placement_date` (datetime, обязательный) - дата размещения - `cost` (float, optional) - стоимость размещения - `comment` (string, optional) - комментарий - `ad_post_url` (string, optional) - ссылка на рекламный пост - `invite_link_type` (enum, optional, default="public") - тип пригласительной ссылки **Response 200:** объект `PlacementOutput` ### PATCH `/api/v1/placements/{placement_id}` Обновить размещение. **Headers:** - `Authorization: Bearer ` **Path параметры:** - `placement_id` (UUID) - ID размещения **Request body:** ```json { "placement_date": "2024-01-02T12:00:00Z", "cost": 1500.0, "comment": "Новый комментарий", "ad_post_url": "https://t.me/channel/124", "status": "archived" } ``` **Все поля опциональны.** **Response 200:** объект `PlacementOutput` ### DELETE `/api/v1/placements/{placement_id}` Удалить размещение. **Headers:** - `Authorization: Bearer ` **Path параметры:** - `placement_id` (UUID) - ID размещения **Response 200:** пустой ответ --- ## Просмотры (Views) Работа с просмотрами рекламных постов. ### GET `/api/v1/placements/{placement_id}/views/history` Получить историю просмотров размещения. **Headers:** - `Authorization: Bearer ` **Path параметры:** - `placement_id` (UUID) - ID размещения **Query параметры:** - `from_date` (datetime, optional) - начало периода - `to_date` (datetime, optional) - конец периода **Response 200:** ```json { "histories": [ { "id": "uuid", "placement_id": "uuid", "views_count": 1500, "fetched_at": "2024-01-02T10:00:00Z", "created_at": "2024-01-02T10:00:00Z" } ] } ``` ### POST `/api/v1/placements/{placement_id}/views/fetch` Получить актуальные просмотры через Telegram API (запросить обновление). **Headers:** - `Authorization: Bearer ` **Path параметры:** - `placement_id` (UUID) - ID размещения **Response 200:** ```json { "placement_id": "uuid", "views_count": 1500, "views_availability": "available", "fetched_at": "2024-01-02T10:00:00Z", "error_message": null } ``` **При ошибке получения просмотров:** ```json { "placement_id": "uuid", "views_count": null, "views_availability": "unavailable", "fetched_at": "2024-01-02T10:00:00Z", "error_message": "Не удалось получить просмотры: нет доступа к каналу" } ``` ### POST `/api/v1/placements/{placement_id}/views/manual` Обновить просмотры вручную (когда автоматическое получение недоступно). **Headers:** - `Authorization: Bearer ` **Path параметры:** - `placement_id` (UUID) - ID размещения **Query параметры:** - `views_count` (integer, обязательный) - количество просмотров **Response 200:** объект `PlacementOutput` (обновленное размещение) --- ## Типы данных и enum ### DateTime Все даты в формате ISO 8601 с timezone (UTC): `2024-01-01T12:00:00Z` ### UUID Строковое представление UUID: `"550e8400-e29b-41d4-a716-446655440000"` ### CreativeStatus (enum) - `"active"` - активный - `"archived"` - архивный ### PlacementStatus (enum) - `"active"` - активное - `"archived"` - архивное ### InviteLinkType (enum) - `"public"` - открытая публичная ссылка - `"approval"` - ссылка с одобрением ботом ### PostViewsAvailability (enum) - `"unknown"` - статус не определен - `"available"` - просмотры доступны через API - `"unavailable"` - просмотры недоступны - `"manual"` - просмотры вводятся вручную --- ## Обработка ошибок ### Формат ошибки Все ошибки возвращаются в стандартном формате FastAPI: ```json { "detail": "Описание ошибки" } ``` ### HTTP коды ответов - `200` - успешный запрос - `401` - не авторизован (невалидный/истекший токен) - `403` - доступ запрещен (нет прав на ресурс) - `404` - ресурс не найден - `422` - ошибка валидации входных данных - `500` - внутренняя ошибка сервера ### Авторизация Для всех защищенных эндпоинтов (все, кроме `/api/v1/auth/complete`) необходимо передавать JWT токен: ``` Authorization: Bearer ``` При отсутствии или невалидном токене вернется `401 Unauthorized`. --- ## Примечания 1. **Soft delete**: При удалении сущностей (креативы, размещения, внешние каналы) они помечаются как удаленные (`deleted_at`), но не удаляются физически из БД. 2. **Отслеживание подписчиков**: Работает только для размещений с `invite_link_type: "approval"`. Бот автоматически одобряет запросы на вступление и увеличивает счетчик подписок. 3. **Автоматическое обновление просмотров**: Фоновый worker периодически обновляет просмотры для активных размещений. Можно также запросить обновление вручную через `/api/v1/placements/{id}/views/fetch`. 4. **Целевые каналы подключаются только через Telegram бота**: Нельзя создать целевой канал через API. Нужно добавить бота в канал как администратора, и он автоматически зарегистрирует канал в системе.