740 lines
11 KiB
Markdown
740 lines
11 KiB
Markdown
# API Документация
|
||
|
||
## Базовый URL
|
||
|
||
```
|
||
https://api.tgex.app/v1
|
||
```
|
||
|
||
## Аутентификация
|
||
|
||
Все запросы (кроме `/auth/*`) требуют JWT токен в заголовке:
|
||
|
||
```
|
||
Authorization: Bearer <token>
|
||
```
|
||
|
||
---
|
||
|
||
## 1. Аутентификация
|
||
|
||
### POST `/auth/init`
|
||
|
||
Инициализация процесса авторизации через Telegram
|
||
|
||
**Response:**
|
||
|
||
```typescript
|
||
{
|
||
bot_username: string;
|
||
start_param: string; // "login"
|
||
}
|
||
```
|
||
|
||
### POST `/auth/complete`
|
||
|
||
Завершение авторизации с одноразовым токеном
|
||
|
||
**Request:**
|
||
|
||
```typescript
|
||
{
|
||
token: string; // one-time token from bot
|
||
}
|
||
```
|
||
|
||
**Response:**
|
||
|
||
```typescript
|
||
{
|
||
access_token: string;
|
||
refresh_token: string;
|
||
user: User;
|
||
}
|
||
```
|
||
|
||
### POST `/auth/refresh`
|
||
|
||
Обновление access токена
|
||
|
||
**Request:**
|
||
|
||
```typescript
|
||
{
|
||
refresh_token: string;
|
||
}
|
||
```
|
||
|
||
**Response:**
|
||
|
||
```typescript
|
||
{
|
||
access_token: string;
|
||
}
|
||
```
|
||
|
||
### GET `/auth/me`
|
||
|
||
Получение информации о текущем пользователе
|
||
|
||
**Response:**
|
||
|
||
```typescript
|
||
User;
|
||
```
|
||
|
||
---
|
||
|
||
## 2. Типы данных
|
||
|
||
### User
|
||
|
||
```typescript
|
||
{
|
||
id: string;
|
||
telegram_id: number;
|
||
username: string | null;
|
||
first_name: string;
|
||
last_name: string | null;
|
||
created_at: string; // ISO 8601
|
||
}
|
||
```
|
||
|
||
### TargetChannel
|
||
|
||
```typescript
|
||
{
|
||
id: string;
|
||
telegram_id: number;
|
||
title: string;
|
||
username: string | null; // @channel_name
|
||
description: string | null;
|
||
is_active: boolean;
|
||
created_at: string;
|
||
updated_at: string;
|
||
// Stats
|
||
total_purchases: number;
|
||
total_subscriptions: number;
|
||
avg_cpf: number | null; // Average Cost Per Follower
|
||
}
|
||
```
|
||
|
||
### ExternalChannel
|
||
|
||
```typescript
|
||
{
|
||
id: string;
|
||
telegram_id: number | null;
|
||
title: string;
|
||
username: string | null;
|
||
link: string; // t.me link or custom
|
||
subscribers_count: number | null;
|
||
description: string | null;
|
||
created_at: string;
|
||
updated_at: string;
|
||
// Relations
|
||
target_channels: string[]; // IDs of target channels
|
||
// Stats
|
||
total_purchases: number;
|
||
avg_cpf: number | null;
|
||
avg_cpm: number | null;
|
||
}
|
||
```
|
||
|
||
### Creative
|
||
|
||
```typescript
|
||
{
|
||
id: string;
|
||
name: string;
|
||
text: string; // Template with {link} placeholder
|
||
target_channel_id: string;
|
||
is_archived: boolean;
|
||
created_at: string;
|
||
updated_at: string;
|
||
// Relations
|
||
target_channel: TargetChannel;
|
||
// Stats
|
||
total_purchases: number;
|
||
total_subscriptions: number;
|
||
avg_cpf: number | null;
|
||
}
|
||
```
|
||
|
||
### Purchase
|
||
|
||
```typescript
|
||
{
|
||
id: string;
|
||
target_channel_id: string;
|
||
external_channel_id: string;
|
||
creative_id: string;
|
||
// Purchase details
|
||
scheduled_date: string | null; // ISO 8601
|
||
actual_date: string | null;
|
||
cost: number | null; // in rubles
|
||
comment: string | null;
|
||
post_link: string | null; // Link to ad post in external channel
|
||
invite_link: string; // Generated invite link
|
||
invite_link_type: "public" | "private"; // with bot approval or not
|
||
is_archived: boolean;
|
||
created_at: string;
|
||
updated_at: string;
|
||
// Relations
|
||
target_channel: TargetChannel;
|
||
external_channel: ExternalChannel;
|
||
creative: Creative;
|
||
// Stats
|
||
subscriptions_count: number;
|
||
views_count: number | null;
|
||
cpf: number | null; // Cost Per Follower
|
||
cpm: number | null; // Cost Per Mile (1000 views)
|
||
conversion_rate: number | null; // subscriptions / views * 100
|
||
}
|
||
```
|
||
|
||
### Subscription
|
||
|
||
```typescript
|
||
{
|
||
id: string;
|
||
purchase_id: string;
|
||
telegram_user_id: number;
|
||
username: string | null;
|
||
joined_at: string; // ISO 8601
|
||
event_type: "chat_member" | "join_request";
|
||
}
|
||
```
|
||
|
||
### ViewsSnapshot
|
||
|
||
```typescript
|
||
{
|
||
id: string;
|
||
purchase_id: string;
|
||
views: number;
|
||
fetched_at: string; // ISO 8601
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 3. Целевые каналы
|
||
|
||
### GET `/target-channels`
|
||
|
||
Получение списка целевых каналов
|
||
|
||
**Query params:**
|
||
|
||
- `is_active?: boolean` - фильтр по активности
|
||
|
||
**Response:**
|
||
|
||
```typescript
|
||
{
|
||
data: TargetChannel[];
|
||
total: number;
|
||
}
|
||
```
|
||
|
||
### GET `/target-channels/:id`
|
||
|
||
Получение детальной информации о канале
|
||
|
||
**Response:**
|
||
|
||
```typescript
|
||
TargetChannel & {
|
||
recent_purchases: Purchase[]; // last 5
|
||
stats_by_period: {
|
||
period: 'day' | 'week' | 'month';
|
||
subscriptions: number;
|
||
cost: number;
|
||
cpf: number;
|
||
}[];
|
||
}
|
||
```
|
||
|
||
### PATCH `/target-channels/:id`
|
||
|
||
Обновление канала (только is_active)
|
||
|
||
**Request:**
|
||
|
||
```typescript
|
||
{
|
||
is_active: boolean;
|
||
}
|
||
```
|
||
|
||
**Response:**
|
||
|
||
```typescript
|
||
TargetChannel;
|
||
```
|
||
|
||
### DELETE `/target-channels/:id`
|
||
|
||
Отключение канала (soft delete)
|
||
|
||
**Response:**
|
||
|
||
```typescript
|
||
{
|
||
success: true;
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 4. Внешние каналы
|
||
|
||
### GET `/external-channels`
|
||
|
||
Получение списка внешних каналов
|
||
|
||
**Query params:**
|
||
|
||
- `target_channel_id?: string` - фильтр по целевому каналу
|
||
- `search?: string` - поиск по названию/username
|
||
|
||
**Response:**
|
||
|
||
```typescript
|
||
{
|
||
data: ExternalChannel[];
|
||
total: number;
|
||
}
|
||
```
|
||
|
||
### GET `/external-channels/:id`
|
||
|
||
Получение детальной информации
|
||
|
||
**Response:**
|
||
|
||
```typescript
|
||
ExternalChannel & {
|
||
purchases: Purchase[];
|
||
}
|
||
```
|
||
|
||
### POST `/external-channels`
|
||
|
||
Создание нового внешнего канала
|
||
|
||
**Request:**
|
||
|
||
```typescript
|
||
{
|
||
title: string;
|
||
username?: string;
|
||
link: string;
|
||
subscribers_count?: number;
|
||
description?: string;
|
||
target_channel_ids: string[]; // Привязка к целевым каналам
|
||
}
|
||
```
|
||
|
||
**Response:**
|
||
|
||
```typescript
|
||
ExternalChannel;
|
||
```
|
||
|
||
### PATCH `/external-channels/:id`
|
||
|
||
Обновление внешнего канала
|
||
|
||
**Request:**
|
||
|
||
```typescript
|
||
Partial<{
|
||
title: string;
|
||
username: string;
|
||
link: string;
|
||
subscribers_count: number;
|
||
description: string;
|
||
target_channel_ids: string[];
|
||
}>;
|
||
```
|
||
|
||
**Response:**
|
||
|
||
```typescript
|
||
ExternalChannel;
|
||
```
|
||
|
||
### DELETE `/external-channels/:id`
|
||
|
||
Удаление внешнего канала
|
||
|
||
**Response:**
|
||
|
||
```typescript
|
||
{
|
||
success: true;
|
||
}
|
||
```
|
||
|
||
### POST `/external-channels/import`
|
||
|
||
Импорт из Excel (Tgstat)
|
||
|
||
**Request:**
|
||
|
||
```
|
||
Content-Type: multipart/form-data
|
||
file: File (Excel)
|
||
target_channel_id: string
|
||
```
|
||
|
||
**Response:**
|
||
|
||
```typescript
|
||
{
|
||
imported: number;
|
||
skipped: number;
|
||
errors: string[];
|
||
channels: ExternalChannel[];
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 5. Креативы
|
||
|
||
### GET `/creatives`
|
||
|
||
Получение списка креативов
|
||
|
||
**Query params:**
|
||
|
||
- `target_channel_id?: string` - фильтр по целевому каналу
|
||
- `is_archived?: boolean` - фильтр по архивности
|
||
|
||
**Response:**
|
||
|
||
```typescript
|
||
{
|
||
data: Creative[];
|
||
total: number;
|
||
}
|
||
```
|
||
|
||
### GET `/creatives/:id`
|
||
|
||
Получение детальной информации
|
||
|
||
**Response:**
|
||
|
||
```typescript
|
||
Creative & {
|
||
purchases: Purchase[];
|
||
}
|
||
```
|
||
|
||
### POST `/creatives`
|
||
|
||
Создание нового креатива
|
||
|
||
**Request:**
|
||
|
||
```typescript
|
||
{
|
||
name: string;
|
||
text: string; // Must contain {link} placeholder
|
||
target_channel_id: string;
|
||
}
|
||
```
|
||
|
||
**Response:**
|
||
|
||
```typescript
|
||
Creative;
|
||
```
|
||
|
||
### PATCH `/creatives/:id`
|
||
|
||
Обновление креатива
|
||
|
||
**Request:**
|
||
|
||
```typescript
|
||
Partial<{
|
||
name: string;
|
||
text: string;
|
||
is_archived: boolean;
|
||
}>;
|
||
```
|
||
|
||
**Response:**
|
||
|
||
```typescript
|
||
Creative;
|
||
```
|
||
|
||
### DELETE `/creatives/:id`
|
||
|
||
Удаление креатива (только если нет активных закупов)
|
||
|
||
**Response:**
|
||
|
||
```typescript
|
||
{
|
||
success: true;
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 6. Закупы
|
||
|
||
### GET `/purchases`
|
||
|
||
Получение списка закупов
|
||
|
||
**Query params:**
|
||
|
||
- `target_channel_id?: string`
|
||
- `external_channel_id?: string`
|
||
- `creative_id?: string`
|
||
- `is_archived?: boolean`
|
||
- `date_from?: string` (ISO 8601)
|
||
- `date_to?: string` (ISO 8601)
|
||
- `sort?: 'date' | 'cost' | 'cpf' | 'subscriptions'`
|
||
- `order?: 'asc' | 'desc'`
|
||
|
||
**Response:**
|
||
|
||
```typescript
|
||
{
|
||
data: Purchase[];
|
||
total: number;
|
||
}
|
||
```
|
||
|
||
### GET `/purchases/:id`
|
||
|
||
Получение детальной информации о закупе
|
||
|
||
**Response:**
|
||
|
||
```typescript
|
||
Purchase & {
|
||
subscriptions: Subscription[];
|
||
views_history: ViewsSnapshot[];
|
||
}
|
||
```
|
||
|
||
### POST `/purchases`
|
||
|
||
Создание нового закупа
|
||
|
||
**Request:**
|
||
|
||
```typescript
|
||
{
|
||
target_channel_id: string;
|
||
external_channel_id: string;
|
||
creative_id: string;
|
||
scheduled_date?: string;
|
||
actual_date?: string;
|
||
cost?: number;
|
||
comment?: string;
|
||
post_link?: string;
|
||
invite_link_type: 'public' | 'private';
|
||
}
|
||
```
|
||
|
||
**Response:**
|
||
|
||
```typescript
|
||
Purchase & {
|
||
formatted_message: string; // Creative text with invite_link inserted
|
||
}
|
||
```
|
||
|
||
### PATCH `/purchases/:id`
|
||
|
||
Обновление закупа
|
||
|
||
**Request:**
|
||
|
||
```typescript
|
||
Partial<{
|
||
scheduled_date: string;
|
||
actual_date: string;
|
||
cost: number;
|
||
comment: string;
|
||
post_link: string;
|
||
is_archived: boolean;
|
||
}>;
|
||
```
|
||
|
||
**Response:**
|
||
|
||
```typescript
|
||
Purchase;
|
||
```
|
||
|
||
### DELETE `/purchases/:id`
|
||
|
||
Удаление закупа
|
||
|
||
**Response:**
|
||
|
||
```typescript
|
||
{
|
||
success: true;
|
||
}
|
||
```
|
||
|
||
### POST `/purchases/:id/refresh-views`
|
||
|
||
Принудительное обновление просмотров
|
||
|
||
**Response:**
|
||
|
||
```typescript
|
||
{
|
||
views: number;
|
||
fetched_at: string;
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 7. Аналитика
|
||
|
||
### GET `/analytics/overview`
|
||
|
||
Общая статистика
|
||
|
||
**Query params:**
|
||
|
||
- `target_channel_id?: string`
|
||
- `date_from?: string`
|
||
- `date_to?: string`
|
||
|
||
**Response:**
|
||
|
||
```typescript
|
||
{
|
||
total_purchases: number;
|
||
total_cost: number;
|
||
total_subscriptions: number;
|
||
avg_cpf: number;
|
||
avg_cpm: number;
|
||
avg_conversion_rate: number;
|
||
}
|
||
```
|
||
|
||
### GET `/analytics/costs`
|
||
|
||
Данные для графика затрат
|
||
|
||
**Query params:**
|
||
|
||
- `target_channel_id?: string`
|
||
- `date_from: string` (required)
|
||
- `date_to: string` (required)
|
||
- `group_by: 'day' | 'week' | 'month' | 'quarter' | 'year'`
|
||
|
||
**Response:**
|
||
|
||
```typescript
|
||
{
|
||
data: {
|
||
period: string; // ISO 8601 date (start of period)
|
||
cost: number;
|
||
purchases_count: number;
|
||
subscriptions: number;
|
||
}
|
||
[];
|
||
}
|
||
```
|
||
|
||
### GET `/analytics/channels`
|
||
|
||
Аналитика по каналам
|
||
|
||
**Query params:**
|
||
|
||
- `target_channel_id?: string`
|
||
- `date_from?: string`
|
||
- `date_to?: string`
|
||
- `type: 'external' | 'target'`
|
||
|
||
**Response:**
|
||
|
||
```typescript
|
||
{
|
||
data: {
|
||
channel_id: string;
|
||
channel_name: string;
|
||
purchases_count: number;
|
||
total_cost: number;
|
||
total_subscriptions: number;
|
||
avg_cpf: number;
|
||
avg_cpm: number;
|
||
}
|
||
[];
|
||
}
|
||
```
|
||
|
||
### GET `/analytics/creatives`
|
||
|
||
Аналитика по креативам
|
||
|
||
**Query params:**
|
||
|
||
- `target_channel_id?: string`
|
||
- `date_from?: string`
|
||
- `date_to?: string`
|
||
|
||
**Response:**
|
||
|
||
```typescript
|
||
{
|
||
data: {
|
||
creative_id: string;
|
||
creative_name: string;
|
||
purchases_count: number;
|
||
total_cost: number;
|
||
total_subscriptions: number;
|
||
avg_cpf: number;
|
||
conversion_rate: number;
|
||
}
|
||
[];
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 8. Коды ошибок
|
||
|
||
```typescript
|
||
{
|
||
error: {
|
||
code: string;
|
||
message: string;
|
||
details?: any;
|
||
}
|
||
}
|
||
```
|
||
|
||
Основные коды:
|
||
|
||
- `UNAUTHORIZED` - 401
|
||
- `FORBIDDEN` - 403
|
||
- `NOT_FOUND` - 404
|
||
- `VALIDATION_ERROR` - 422
|
||
- `INTERNAL_ERROR` - 500
|
||
- `CHANNEL_NOT_FOUND` - 404
|
||
- `CHANNEL_ALREADY_EXISTS` - 409
|
||
- `INSUFFICIENT_PERMISSIONS` - 403
|
||
- `CREATIVE_IN_USE` - 409
|
||
- `INVALID_INVITE_LINK` - 422
|