> For the complete documentation index, see [llms.txt](https://incy.gitbook.io/docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://incy.gitbook.io/docs/premium-api.md).

# Premium API

### Обзор

Приложение при добавлении подписки извлекает домен из URL и запрашивает конфигурацию провайдера через зашифрованный API. Домен никогда не передаётся в открытом виде — используется SHA-256 хеш.

***

### API

#### Endpoint

```
GET /api/subscription/config?h=<sha256hex>&hwid=<sha256hex>
```

#### Запрос

| Параметр | Тип    | Обязательный | Описание                                                                    |
| -------- | ------ | ------------ | --------------------------------------------------------------------------- |
| `h`      | string | да           | SHA-256 хеш домена (64 hex-символа)                                         |
| `hwid`   | string | нет          | SHA-256 хеш HWID устройства (64 hex-символа). Для проверки лимита устройств |

Хеш вычисляется от домена в нижнем регистре:

```
SHA256("example.com") → "a379a6f6eeafb9a55e378c118034e2751e682fab9f2d30ab13d2125586ce1947"
```

#### Ответ

Сервер всегда возвращает зашифрованный ответ:

```json
{
    "encrypted": true,
    "iv": "<base64>",
    "data": "<base64>",
    "tag": "<base64>"
}
```

#### Поля ответа (после расшифровки)

| Поле                  | Тип     | Описание                                                   |
| --------------------- | ------- | ---------------------------------------------------------- |
| `success`             | boolean | Успешность запроса                                         |
| `domain`              | string  | Домен подписки                                             |
| `isPremium`           | boolean | Активен ли Premium у провайдера                            |
| `deviceLimitExceeded` | boolean | Лимит устройств провайдера превышен (см. Лимиты устройств) |
| `logoUrl`             | string? | URL логотипа провайдера                                    |
| `settings`            | object  | Настройки провайдера (см. ниже)                            |
| `theme`               | object  | Кастомная тема (см. ниже)                                  |

#### Rate Limit

30 запросов в минуту на IP-адрес. При превышении — ответ `429`.

***

### Шифрование

Ответы API зашифрованы алгоритмом AES-256-GCM. Ключ шифрования деривируется на основе домена — только клиент, знающий оригинальный домен и комбинацию, может расшифровать ответ.

Компоненты ответа:

| Поле   | Описание                                |
| ------ | --------------------------------------- |
| `iv`   | Initialization vector (base64, 12 байт) |
| `data` | Зашифрованные данные (base64)           |
| `tag`  | Authentication tag (base64, 16 байт)    |

> Детали деривации ключа и реализация шифрования являются внутренними и не публикуются.

***

### Структура конфигурации

#### Настройки провайдера (`settings`)

**Базовые**

| Поле                    | Тип     | Описание                                                       |
| ----------------------- | ------- | -------------------------------------------------------------- |
| `serverDescription`     | string  | Описание сервера (до 30 символов)                              |
| `alwaysHwidEnable`      | boolean | Принудительная отправка HWID (пользователь не может отключить) |
| `expiryNotifications`   | boolean | Локальные уведомления об истечении подписки                    |
| `showServerDescription` | boolean | Показывать описание серверов. По умолчанию `true`              |

**Domain fronting и фрагментация**

| Поле               | Тип     | Описание                                    |
| ------------------ | ------- | ------------------------------------------- |
| `resolveAddress`   | string  | IP для domain fronting                      |
| `hostHeader`       | string  | Host-заголовок для domain fronting          |
| `fragmentEnabled`  | boolean | TCP-фрагментация в hev-socks5-tunnel        |
| `fragmentLength`   | string  | Диапазон длины фрагментов (напр. `"10-30"`) |
| `fragmentInterval` | string  | Диапазон интервала (напр. `"20-40"` мс)     |
| `fragmentPackets`  | string  | Количество фрагментов (напр. `"5-10"`)      |

**DNS-резолв адреса сервера (DoH)**

| Поле                            | Тип     | Описание                                                              |
| ------------------------------- | ------- | --------------------------------------------------------------------- |
| `serverAddressResolveEnable`    | boolean | Предварительный резолв адреса сервера через DoH                       |
| `serverAddressResolveDnsDomain` | string  | URL DoH-сервера (напр. `https://common.dot.dns.yandex.net/dns-query`) |
| `serverAddressResolveDnsIp`     | string  | IP DoH-сервера (используется до резолва его домена)                   |

**Lite Mode**

Упрощённый интерфейс с ссылками на бот, канал, поддержку и (опционально) премиум. Подробнее про иконки — icon-presets.md.

| Поле             | Тип     | Описание                                                |
| ---------------- | ------- | ------------------------------------------------------- |
| `liteMode`       | boolean | Включить упрощённый режим                               |
| `liteBigButton`  | boolean | Большая кнопка подключения                              |
| `botUrl`         | string? | Ссылка на Telegram-бот                                  |
| `channelUrl`     | string? | Ссылка на Telegram-канал                                |
| `supportUrl`     | string? | Ссылка на поддержку                                     |
| `botIconKey`     | string? | Ключ иконки бота из пресет-набора (см. icon-presets.md) |
| `channelIconKey` | string? | Ключ иконки канала                                      |
| `supportIconKey` | string? | Ключ иконки поддержки                                   |

> `null` или неизвестный ключ → клиент использует дефолтную иконку (`send` для бота, `megaphone` для канала, `help` для поддержки).

**Админ-доступ (admin HWIDs)**

Устройства в списке `adminHwids` могут:

* Просматривать и редактировать конфиги серверов прямо в приложении.
* Отправлять провайдерские уведомления **без модерации** (auto-approve при совпадении с `targetSegment.hwid`).

Подробности — admin-hwids.md.

| Поле         | Тип        | Описание                              |
| ------------ | ---------- | ------------------------------------- |
| `adminHwids` | `string[]` | Список HWID устройств с админ-правами |

**Баннер подписки**

Красный/произвольного цвета баннер внутри карточки подписки. Используется для апсейла / предупреждений об истечении / анонсов.

| Поле                | Тип     | Описание                                                         |
| ------------------- | ------- | ---------------------------------------------------------------- |
| `bannerEnabled`     | boolean | Показывать баннер                                                |
| `bannerText`        | string? | Текст баннера (белым по `bannerBgColor`)                         |
| `bannerButtonText`  | string? | Текст кнопки (если пусто — кнопка скрыта)                        |
| `bannerButtonUrl`   | string? | URL кнопки                                                       |
| `bannerBgColor`     | string? | Цвет фона баннера (hex, напр. `"#E53E3E"`, по умолчанию красный) |
| `bannerButtonColor` | string? | Цвет кнопки (hex, по умолчанию зелёный)                          |

**Принудительные настройки**

Переопределяют выбор пользователя в настройках приложения, пока подписка активна.

| Поле                   | Тип       | Описание                                                                                            |
| ---------------------- | --------- | --------------------------------------------------------------------------------------------------- |
| `forceConnectionStyle` | `string?` | Стиль кнопки подключения: `"classic"` (крупная круглая кнопка) или `"compact"` (узкий toggle внизу) |

**Ping и сортировка**

Применяются при первом подключении подписки и переопределяют дефолтные настройки приложения.

| Поле                  | Тип      | Описание                                            |
| --------------------- | -------- | --------------------------------------------------- |
| `defaultPingProtocol` | string?  | Тип пинга: `tcp`, `proxy_head`, `proxy_get`, `icmp` |
| `defaultSortOrder`    | string?  | Сортировка серверов: `none`, `ping`, `name`         |
| `pingOnUpdate`        | boolean? | Авто-пинг всех серверов после обновления подписки   |

**Контент**

| Поле          | Тип     | Описание                                   |
| ------------- | ------- | ------------------------------------------ |
| `announceUrl` | string? | URL для объявлений провайдера в приложении |
| `webPageUrl`  | string? | Ссылка на веб-страницу провайдера          |

#### Кастомная тема (`theme`)

Расширенная палитра, применяется только когда пользователь видит premium-подписку. Поддерживает плоские цвета и 2-4-стоповые градиенты.

**Плоские цвета**

| Поле              | Тип     | Описание                                         |
| ----------------- | ------- | ------------------------------------------------ |
| `enabled`         | boolean | Кастомная тема включена                          |
| `darkMode`        | boolean | Тёмный режим                                     |
| `accent`          | string  | Основной акцентный цвет (hex, напр. `"#FF6B6B"`) |
| `accentSecondary` | string  | Вторичный акцентный цвет                         |
| `background`      | string  | Цвет фона                                        |
| `card`            | string  | Цвет карточек                                    |
| `surface`         | string  | Цвет поверхности                                 |
| `textPrimary`     | string  | Основной цвет текста                             |
| `textSecondary`   | string  | Вторичный цвет текста                            |

**Градиенты**

Если задан, перекрывает плоские `accent`/`accentSecondary` для акцентных элементов и `background` для фона.

| Поле                         | Тип             | Описание                                                             |
| ---------------------------- | --------------- | -------------------------------------------------------------------- |
| `accentGradient.stops`       | GradientStop\[] | 2-4 стопа (`{ color, position }`), `position` в диапазоне `0.0..1.0` |
| `accentGradient.angle`       | number          | Угол градиента в градусах `0..360`                                   |
| `backgroundGradient.enabled` | boolean         | Включить градиентный фон                                             |
| `backgroundGradient.stops`   | GradientStop\[] | 2-3 стопа                                                            |
| `backgroundGradient.angle`   | number          | Угол градиента                                                       |

Каждый стоп:

```json
{ "color": "#B8D94A", "position": 0.0 }
```

***

### Лимиты устройств

Premium-провайдеры имеют тарифы с ограничением по количеству устройств. Лимит хранится в поле `maxDevices` документа провайдера в Firestore.

#### Как работает

1. Клиент отправляет `hwid=SHA256(rawHwid)` в запросе конфигурации
2. Сервер проверяет, зарегистрировано ли устройство с таким `hwidHash` для доменов данного провайдера
3. Если устройство **уже зарегистрировано** — всегда получает premium (существующие устройства не блокируются)
4. Если устройство **не зарегистрировано** — сервер считает общее количество устройств провайдера:
   * Если `totalDevices < maxDevices` — premium выдаётся
   * Если `totalDevices >= maxDevices` — возвращается `isPremium: false` + `deviceLimitExceeded: true`

#### Поведение клиента при `deviceLimitExceeded: true`

* Premium-функции отключены (как при `isPremium: false`)
* Кастомная тема провайдера не применяется
* Premium-настройки (фрагментация, fronting и т.д.) не используются
* VPN продолжает работать в базовом режиме
* Устройство регистрируется в Firebase (но без premium)

#### Тарифы

| Лимит  | Описание                      |
| ------ | ----------------------------- |
| `1000` | Стандартный тариф             |
| `3000` | Средний тариф                 |
| `6000` | Максимальный тариф            |
| `null` | Безлимитный (нет ограничений) |

***

### Регистрация устройства

При добавлении подписки приложение регистрирует устройство для отслеживания активных подключений и push-уведомлений.

#### Данные регистрации

| Поле                     | Тип       | Описание                                          |
| ------------------------ | --------- | ------------------------------------------------- |
| `hwid`                   | string    | Аппаратный идентификатор устройства (подробнее)   |
| `hwidHash`               | string    | SHA-256 хеш HWID (для серверной проверки лимитов) |
| `uid`                    | string    | Firebase anonymous UID                            |
| `platform`               | string    | `"android"`, `"ios"`, `"linux"`, `"windows"`      |
| `appVersion`             | string    | Версия приложения                                 |
| `osVersion`              | string    | Версия ОС                                         |
| `locale`                 | string    | Язык устройства                                   |
| `subscriptionDomainHash` | string    | SHA-256 хеш домена подписки                       |
| `fcmToken`               | string    | Firebase Cloud Messaging токен                    |
| `lastActive`             | timestamp | Время последней активности                        |

> Поле `hwidHash` добавляется при регистрации и используется сервером для проверки лимита устройств без знания исходного HWID.

#### Синхронизация подписки

Сервер может установить флаг `subscriptionNeedsSync = true` на устройстве. При обнаружении этого флага приложение:

1. Читает поле `subscriptionNewDomain` из документа устройства
2. Обновляет URL подписки на новый домен
3. Сбрасывает флаг `subscriptionNeedsSync`

Это позволяет провайдеру мигрировать пользователей на новый домен при блокировке старого.

***

### Кеширование

* **In-memory кеш:** конфигурация хранится в памяти на время работы приложения
* **Disk кеш:** сохраняется в SecureStorage для offline-доступа
* **Fallback:** при ошибке `503` используется кешированная конфигурация
* **Очистка:** конфигурация удаляется из кеша, если домен больше не является premium

***

### Certificate Pinning

Запросы к Premium API на Android и Desktop защищены certificate pinning (SHA-256 пины TLS-сертификата). Это предотвращает MITM-атаки на канал связи с API.

***

#### Firestore индекс

Для корректной работы проверки лимитов необходим составной индекс:

* **Коллекция:** `devices`
* **Поля:** `hwidHash` (ASC), `subscriptionDomainHash` (ASC)\`
