# API документация 3dp-manager **Версия API:** v1 **Базовый URL:** `http://:3100/api` **Префикс:** Все endpoints начинаются с `/api`, кроме публичных подписок (`/bus/:uuid`) --- ## Содержание 1. [Аутентификация](#аутентификация) 2. [Подписки](#подписки) 3. [Домены](#домены) 4. [Туннели (перенаправление)](#туннели-перенаправление) 5. [Настройки](#настройки) 6. [Ротация](#ротация) 7. [Клиентские endpoints (публичные)](#клиентские-endpoints-публичные) --- ## Аутентификация Все endpoints требуют JWT-аутентификации, кроме помеченных как **Public**. ### POST `/api/auth/login` Получение JWT-токена. **Body:** ```json { "login": "admin", "password": "your_password" } ``` **Response:** ```json { "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." } ``` --- ### POST `/api/auth/change-password` Смена пароля администратора. **Headers:** `Authorization: Bearer ` **Body:** ```json { "password": "newSecurePassword" } ``` **Response:** ```json { "success": true } ``` --- ### POST `/api/auth/update-profile` Обновление профиля администратора (логин + опционально пароль). **Headers:** `Authorization: Bearer ` **Body:** ```json { "login": "newAdmin", "password": "newPassword" } ``` **Response:** ```json { "success": true } ``` --- ## Подписки ### GET `/api/subscriptions` Получить все подписки. **Headers:** `Authorization: Bearer ` **Response:** ```json [ { "id": 1, "uuid": "abc-123-def", "name": "My Subscription", "isEnabled": true, "isAutoRotationEnabled": true, "inbounds": [...], "createdAt": "2024-01-01T00:00:00Z", "updatedAt": "2024-01-01T00:00:00Z" } ] ``` --- ### POST `/api/subscriptions` Создать новую подписку. **Headers:** `Authorization: Bearer ` **Body:** ```json { "name": "My Subscription", "inboundsConfig": [ { "type": "vless", "port": 443, "sni": "example.com" }, { "type": "vmess", "port": 8443, "transport": "websocket" } ], "isAutoRotationEnabled": true } ``` **Response:** ```json { "id": 1, "uuid": "abc-123-def", "name": "My Subscription", ... } ``` --- ### PUT `/api/subscriptions/:id` Обновить подписку. **Headers:** `Authorization: Bearer ` **Body:** ```json { "name": "Updated Name", "isEnabled": false, "isAutoRotationEnabled": true } ``` **Response:** ```json { "id": 1, "name": "Updated Name", ... } ``` --- ### PUT `/api/subscriptions/bulk-auto-rotation` Массовое включение/отключение авто-ротации. **Headers:** `Authorization: Bearer ` **Body:** ```json { "subscriptionIds": ["1", "2", "3"], "enabled": true } ``` **Response:** ```json { "success": true, "message": "Обновлено 3 подписок", "updatedCount": 3, "notFound": [] } ``` --- ### DELETE `/api/subscriptions/:id` Удалить подписку. **Headers:** `Authorization: Bearer ` **Response:** ```json { "success": true } ``` --- ## Домены ### GET `/api/domains/all` Получить все домены без пагинации. **Headers:** `Authorization: Bearer ` **Response:** ```json [ { "id": 1, "name": "ya.ru" }, { "id": 2, "name": "vk.com" } ] ``` --- ### GET `/api/domains?page=1&limit=10` Получить домены с пагинацией. **Headers:** `Authorization: Bearer ` **Query Parameters:** - `page` (default: 1) - `limit` (default: 10) **Response:** ```json { "data": [ { "id": 1, "name": "ya.ru" } ], "total": 100 } ``` --- ### GET `/api/domains/:id` Получить домен по ID. **Headers:** `Authorization: Bearer ` **Response:** ```json { "id": 1, "name": "ya.ru" } ``` --- ### POST `/api/domains` Создать один домен. **Headers:** `Authorization: Bearer ` **Body:** ```json { "name": "example.com" } ``` **Response:** ```json { "id": 1, "name": "example.com" } ``` --- ### POST `/api/domains/upload` Загрузить несколько доменов сразу. **Headers:** `Authorization: Bearer ` **Body:** ```json { "domains": ["ya.ru", "vk.com", "example.com"] } ``` **Response:** ```json { "count": 3 } ``` --- ### DELETE `/api/domains/:id` Удалить домен по ID. **Headers:** `Authorization: Bearer ` **Response:** ```json { "success": true } ``` --- ### DELETE `/api/domains/all` Удалить все домены. **Headers:** `Authorization: Bearer ` **Response:** ```json { "success": true } ``` --- ### GET `/api/domains/scan/capabilities` Получить возможности сканера доменов (наличие бинарника, timeout). **Headers:** `Authorization: Bearer ` **Response:** ```json { "scannerAvailable": true, "scannerPath": "/usr/bin/RealiTLScanner-linux-64", "timeoutAvailable": true, "timeoutPath": "/usr/bin/timeout" } ``` --- ### GET `/api/domains/scan/status` Получить статус текущего сканирования. **Headers:** `Authorization: Bearer ` **Response:** ```json { "running": false, "runId": null, "lastRunId": "scan-123" } ``` --- ### GET `/api/domains/scan/last-result` Получить результат последнего сканирования. **Headers:** `Authorization: Bearer ` **Response:** ```json { "runId": "scan-123", "foundCount": 10, "domains": ["ya.ru", "vk.com", ...] } ``` --- ### POST `/api/domains/scan/start` Запустить сканирование IP на наличие SNI. **Headers:** `Authorization: Bearer ` **Body:** ```json { "addr": "192.168.1.1", "scanSeconds": 60, "thread": 100, "timeout": 30 } ``` **Response:** ```json { "success": true, "runId": "scan-456" } ``` --- ## Туннели (перенаправление) ### GET `/api/tunnels` Получить все туннели. **Headers:** `Authorization: Bearer ` **Response:** ```json [ { "id": 1, "name": "Relay Server 1", "ip": "192.168.1.100", "sshPort": 22, "username": "root", "domain": "relay.example.com", "isInstalled": true } ] ``` --- ### POST `/api/tunnels` Создать новый туннель. **Headers:** `Authorization: Bearer ` **Body:** ```json { "name": "Relay Server 1", "ip": "192.168.1.100", "sshPort": 22, "username": "root", "password": "ssh_password", "domain": "relay.example.com" } ``` **Response:** ```json { "id": 1, "name": "Relay Server 1", ... } ``` --- ### POST `/api/tunnels/:id/install` Установить скрипт перенаправления на удалённый сервер. **Headers:** `Authorization: Bearer ` **Response:** ```json { "success": true, "script": "#!/bin/bash ...", "message": "Скрипт сгенерирован. Выполните его на целевом сервере." } ``` --- ### DELETE `/api/tunnels/:id` Удалить туннель. **Headers:** `Authorization: Bearer ` **Response:** ```json { "success": true } ``` --- ## Настройки ### GET `/api/settings` Получить все настройки. **Headers:** `Authorization: Bearer ` **Response:** ```json { "xui_url": "http://192.168.1.1:54321", "xui_login": "admin", "rotationIntervalMinutes": "1440", "xui_geo_country": "Germany", "xui_geo_flag": "🇩🇪" } ``` --- ### POST `/api/settings` Обновить настройки. **Headers:** `Authorization: Bearer ` **Body:** ```json { "xui_url": "http://192.168.1.1:54321", "xui_login": "admin", "xui_password": "password", "rotationIntervalMinutes": "1440" } ``` **Response:** ```json { "success": true } ``` *Автоматически извлекает host, IP, определяет страну по GeoIP.* --- ### POST `/api/settings/check` Проверить подключение к 3x-ui панели. **Headers:** `Authorization: Bearer ` **Body:** ```json { "xui_url": "http://192.168.1.1:54321", "xui_login": "admin", "xui_password": "password" } ``` **Response:** ```json { "success": true } ``` --- ## Ротация ### POST `/api/rotation/rotate-all` Выполнить ротацию всех подписок. **Headers:** `Authorization: Bearer ` **Response:** ```json { "success": true, "rotatedCount": 5 } ``` --- ### POST `/api/rotation/rotate-one/:id` Выполнить ротацию одной подписки. **Headers:** `Authorization: Bearer ` **Response:** ```json { "success": true, "subscriptionId": "1" } ``` --- ## Клиентские endpoints (публичные) Эти endpoints **не требуют аутентификации**. ### GET `/bus/:uuid` Получить подписку в формате Base64 или HTML-страницу с QR-кодом. **Public** — без токена. **URL:** ``` GET /bus/abc-123-def ``` **Headers:** - `User-Agent: Mozilla/...` → возвращает HTML с QR - `User-Agent: v2rayN/...` → возвращает plain text Base64 **Response (Base64):** ``` vless://abc123... vmess://xyz789... ... ``` **Response (HTML):** ```html

Subscription: My Subscription

Скачать подписку ``` --- ### GET `/bus/:uuid/:tunnelId` Получить подписку с заменой хоста на релей-сервер. **Public** — без токена. **Query Parameters:** - `format` (опционально): `base64` | `html` **URL:** ``` GET /bus/abc-123-def/1 ``` **Response:** - Автоматически заменяет IP/домен в ссылках на `tunnel.domain` или `tunnel.ip` - Возвращает Base64 или HTML с QR --- ## Сущности ### Subscription ```typescript { id: number; uuid: string; name: string; isEnabled: boolean; isAutoRotationEnabled: boolean; inbounds: Inbound[]; createdAt: Date; updatedAt: Date; } ``` ### Inbound ```typescript { id: number; type: 'vless' | 'vmess' | 'shadowsocks' | 'hysteria2' | 'trojan' | 'custom'; port: number | string; sni?: string; link: string; transport?: 'tcp' | 'websocket' | 'grpc' | 'xhttp'; } ``` ### Domain ```typescript { id: number; name: string; } ``` ### Tunnel ```typescript { id: number; name: string; ip: string; sshPort: number; username: string; domain?: string; isInstalled: boolean; } ``` ### Setting ```typescript { key: string; value: string; } ``` --- ## Коды ошибок | Код | Описание | |-----|----------| | 200 | Успех | | 400 | Неверный запрос | | 401 | Неавторизован (нет токена или неверный) | | 403 | Доступ запрещён | | 404 | Ресурс не найден | | 500 | Внутренняя ошибка сервера | --- ## Примеры использования ### JavaScript (Fetch API) ```javascript // Логин const loginResponse = await fetch('http://localhost:3100/api/auth/login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ login: 'admin', password: 'admin' }) }); const { access_token } = await loginResponse.json(); // Получить все подписки const subsResponse = await fetch('http://localhost:3100/api/subscriptions', { headers: { 'Authorization': `Bearer ${access_token}` } }); const subscriptions = await subsResponse.json(); // Создать подписку const createResponse = await fetch('http://localhost:3100/api/subscriptions', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${access_token}` }, body: JSON.stringify({ name: 'My Subscription', inboundsConfig: [ { type: 'vless', port: 443, sni: 'example.com' } ], isAutoRotationEnabled: true }) }); const subscription = await createResponse.json(); ``` ### Python (requests) ```python import requests # Логин response = requests.post('http://localhost:3100/api/auth/login', json={ 'login': 'admin', 'password': 'admin' }) token = response.json()['access_token'] # Получить все домены headers = {'Authorization': f'Bearer {token}'} response = requests.get('http://localhost:3100/api/domains/all', headers=headers) domains = response.json() # Запустить сканирование response = requests.post('http://localhost:3100/api/domains/scan/start', headers=headers, json={ 'addr': '192.168.1.1', 'scanSeconds': 60, 'thread': 100, 'timeout': 30 } ) ``` ### cURL ```bash # Логин curl -X POST http://localhost:3100/api/auth/login \ -H "Content-Type: application/json" \ -d '{"login":"admin","password":"admin"}' # Получить подписку (Base64) curl http://localhost:3100/bus/abc-123-def # Получить подписку (HTML с QR) curl -H "User-Agent: Mozilla/5.0" \ http://localhost:3100/bus/abc-123-def ``` --- ## Интеграция ### Ссылка на подписку в клиенте Формат: ``` http://:/bus/ ``` Пример для v2rayN, Clash, Surge: ``` http://192.168.1.100:3100/bus/abc-123-def ``` ### QR-код Откройте в браузере: ``` http://:/bus/ ``` Сканируйте QR-код приложением. --- ## Примечания - **JWT-токен** передаётся в заголовке `Authorization: Bearer ` - **Публичные endpoints** (`/bus/*`) не требуют токена - **Base64-кодировка** используется для подписок (стандарт v2ray) - **Авто-ротация** выполняется по интервалу из настроек (`rotationIntervalMinutes`) - **Scanner** (RealiTLScanner) — Go-бинарник, встраивается в backend-контейнер --- ## Контакты - Telegram: [@denpiligrim_web](https://t.me/denpiligrim_web) - YouTube: [DenPiligrim](https://www.youtube.com/@denpiligrim) - GitHub: [denpiligrim/3dp-manager](https://github.com/denpiligrim/3dp-manager)