> 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/docs-en/premium-api.md).

# Premium API

Subscription management system with domain-hash-based encryption. Allows providers to customize the app appearance, server settings, and notifications for their users.

## Overview

When adding a subscription, the app extracts the domain from the URL and requests the provider configuration through an encrypted API. The domain is never sent in plain text — a SHA-256 hash is used.

***

## API

### Endpoint

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

### Request

| Parameter | Type   | Required | Description                                                                         |
| --------- | ------ | -------- | ----------------------------------------------------------------------------------- |
| `h`       | string | yes      | SHA-256 hash of the domain (64 hex characters)                                      |
| `hwid`    | string | no       | SHA-256 hash of the device HWID (64 hex characters). Used to check the device limit |

The hash is calculated from the domain in lowercase:

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

### Response

The server always returns an encrypted response:

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

### Response fields (after decryption)

| Field                 | Type    | Description                                                                            |
| --------------------- | ------- | -------------------------------------------------------------------------------------- |
| `success`             | boolean | Request success                                                                        |
| `domain`              | string  | Subscription domain                                                                    |
| `isPremium`           | boolean | Whether the provider's Premium is active                                               |
| `deviceLimitExceeded` | boolean | The provider's device limit has been exceeded (see [Device limits](#лимиты-устройств)) |
| `logoUrl`             | string? | Provider logo URL                                                                      |
| `settings`            | object  | Provider settings (see below)                                                          |
| `theme`               | object  | Custom theme (see below)                                                               |

### Rate Limit

30 requests per minute per IP address. If exceeded — response `429`.

***

## Encryption

API responses are encrypted with the AES-256-GCM algorithm. The encryption key is derived from the domain — only the client that knows the original domain and the combination can decrypt the response.

Response components:

| Field  | Description                              |
| ------ | ---------------------------------------- |
| `iv`   | Initialization vector (base64, 12 bytes) |
| `data` | Encrypted data (base64)                  |
| `tag`  | Authentication tag (base64, 16 bytes)    |

> The key derivation details and encryption implementation are internal and not published.

***

## Configuration structure

### Provider settings (`settings`)

#### Basic

| Field                   | Type    | Description                                         |
| ----------------------- | ------- | --------------------------------------------------- |
| `serverDescription`     | string  | Server description (up to 30 characters)            |
| `alwaysHwidEnable`      | boolean | Forced HWID submission (the user cannot disable it) |
| `expiryNotifications`   | boolean | Local subscription expiration notifications         |
| `showServerDescription` | boolean | Show server descriptions. Default `true`            |

#### Domain fronting and fragmentation

| Field              | Type    | Description                            |
| ------------------ | ------- | -------------------------------------- |
| `resolveAddress`   | string  | IP for domain fronting                 |
| `hostHeader`       | string  | Host header for domain fronting        |
| `fragmentEnabled`  | boolean | TCP fragmentation in hev-socks5-tunnel |
| `fragmentLength`   | string  | Fragment length range (e.g. `"10-30"`) |
| `fragmentInterval` | string  | Interval range (e.g. `"20-40"` ms)     |
| `fragmentPackets`  | string  | Number of fragments (e.g. `"5-10"`)    |

#### DNS resolve of the server address (DoH)

| Field                                              | Type    | Description                                                         |
| -------------------------------------------------- | ------- | ------------------------------------------------------------------- |
| `serverAddressResolveEnable`                       | boolean | Pre-resolve the server address via DoH                              |
| `serverAddressResolveDnsDomain`                    | string  | DoH server URL (e.g. `https://common.dot.dns.yandex.net/dns-query`) |
| `DoH server IP (used before resolving its domain)` | string  | DoH server IP (used before resolving its domain)                    |

#### Lite Mode

Simplified interface with links to the bot, channel, support, and (optionally) premium. More about icons — [icon-presets.md](/docs/docs-en/icon-presets.md).

| Field            | Type    | Description                                                                             |
| ---------------- | ------- | --------------------------------------------------------------------------------------- |
| `liteMode`       | boolean | Enable simplified mode                                                                  |
| `botUrl`         | string? | Link to the Telegram bot                                                                |
| `channelUrl`     | string? | Link to the Telegram channel                                                            |
| `supportUrl`     | string? | Support link                                                                            |
| `botIconKey`     | string? | Bot icon key from the preset set (see [icon-presets.md](/docs/docs-en/icon-presets.md)) |
| `channelIconKey` | string? | Channel icon key                                                                        |
| `supportIconKey` | string? | Support icon key                                                                        |

> `null` or an unknown key → the client uses the default icon (`send` for the bot, `megaphone` for the channel, `help` for support).

#### Admin access (admin HWIDs)

Devices in the list `adminHwids` can:

* View and edit server configs directly in the app.
* Send provider notifications **without moderation** (auto-approve if matched with `targetSegment.hwid`).

Details — [admin-hwids.md](/docs/docs-en/admin-hwids.md).

| Field        | Type       | Description                            |
| ------------ | ---------- | -------------------------------------- |
| `adminHwids` | `string[]` | List of HWID devices with admin rights |

#### Subscription banner

Red/custom-colored banner inside the subscription card. Used for upsells / expiration warnings / announcements.

| Field               | Type    | Description                                                  |
| ------------------- | ------- | ------------------------------------------------------------ |
| `bannerEnabled`     | boolean | Show banner                                                  |
| `bannerText`        | string? | Banner text (white on `bannerBgColor`)                       |
| `bannerButtonText`  | string? | Button text (if empty — the button is hidden)                |
| `bannerButtonUrl`   | string? | Button URL                                                   |
| `bannerBgColor`     | string? | Banner background color (hex, e.g. `"#E53E3E"`, default red) |
| `bannerButtonColor` | string? | Button color (hex, default green)                            |

#### Forced settings

Override the user's selection in the app settings while the subscription is active.

| Field                  | Type      | Description                                                                                          |
| ---------------------- | --------- | ---------------------------------------------------------------------------------------------------- |
| `forceConnectionStyle` | `string?` | Connection button style: `"classic"` (large round button) or `"compact"` (thin toggle at the bottom) |

#### Ping and sorting

Applied on the first subscription connection and override the app's default settings.

| Field                      | Type     | Description                                                                      |
| -------------------------- | -------- | -------------------------------------------------------------------------------- |
| `defaultPingProtocol`      | string?  | Ping type: `incy`, `tcp`, `proxy_head`, `proxy_get`, `icmp`                      |
| `pingTestUrl`              | string?  | URL for HTTP ping (used when `incy` / `proxy_head` / `proxy_get`)                |
| `defaultSortOrder`         | string?  | Server sorting: `none`, `ping`, `name`                                           |
| `defaultPingDisplayFormat` | string?  | Ping display format: `time` (numbers, ms) or `dots` (green dots — responded/not) |
| `pingOnUpdate`             | boolean? | Auto-ping all servers after updating the subscription                            |

`incy` — **INCY Ping**: a real HTTP GET through the proxy, but the value is divided by \~3.3 so it reads like a familiar ping (not a full proxied round-trip). The default method in the app.

#### Content

| Field         | Type    | Description                               |
| ------------- | ------- | ----------------------------------------- |
| `announceUrl` | string? | URL for provider announcements in the app |
| `webPageUrl`  | string? | Link to the provider's web page           |

#### Fallback domains (`fallbackHosts`)

| Field           | Type      | Description                                                                                                                                                                                                                                            |
| --------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `fallbackHosts` | string\[] | List of fallback hosts. If the main subscription domain does not respond during an update, the client retries the same request, replacing these hosts one by one (only the host changes; the path/token stays the same). Processed from top to bottom. |

> The client caches `fallbackHosts` on the subscription, so fallback works even when the main domain is completely unavailable. On `404`/`410` (subscription deleted), fallback is not performed. In the premium panel, it is set with the "Fallback" button next to the domain.

### Custom theme (`theme`)

Extended palette, applied only when the user sees a premium subscription. Supports flat colors and 2-4-stop gradients.

#### Flat colors

| Field             | Type    | Description                                  |
| ----------------- | ------- | -------------------------------------------- |
| `enabled`         | boolean | Custom theme enabled                         |
| `darkMode`        | boolean | Dark mode                                    |
| `accent`          | string  | Primary accent color (hex, e.g. `"#FF6B6B"`) |
| `accentSecondary` | string  | Secondary accent color                       |
| `background`      | string  | Background color                             |
| `card`            | string  | Card color                                   |
| `surface`         | string  | Surface color                                |
| `textPrimary`     | string  | Primary text color                           |
| `textSecondary`   | string  | Secondary text color                         |

#### Gradients

If specified, overrides the flat `accent`/`accentSecondary` for accent elements and `background` for the background.

| Field                        | Type            | Description                                                       |
| ---------------------------- | --------------- | ----------------------------------------------------------------- |
| `accentGradient.stops`       | GradientStop\[] | 2-4 stops (`{ color, position }`), `position` in range `0.0..1.0` |
| `accentGradient.angle`       | number          | Gradient angle in degrees `0..360`                                |
| `backgroundGradient.enabled` | boolean         | Enable gradient background                                        |
| `backgroundGradient.stops`   | GradientStop\[] | 2-3 stops                                                         |
| `backgroundGradient.angle`   | number          | Gradient angle                                                    |

Each stop:

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

***

## Device limits

Premium providers have plans with a device count limit. The limit is set by the field `maxDevices` of the provider.

### How it works

1. The client sends `hwid=SHA256(rawHwid)` in the configuration request
2. The server checks whether a device with this `hwidHash` is registered for the provider's domains
3. If the device **is already registered** — always gets premium (existing devices are not blocked)
4. If the device **is not registered** — the server counts the provider's total devices:
   * If `totalDevices < maxDevices` — premium is granted
   * If `totalDevices >= maxDevices` — returned `isPremium: false` + `deviceLimitExceeded: true`

### Client behavior when `deviceLimitExceeded: true`

* Premium features are disabled (as with `isPremium: false`)
* The provider's custom theme is not applied
* Premium settings (fragmentation, fronting, etc.) are not used
* VPN continues to work in basic mode
* The device is registered (but without premium)

### Plans

| Limit  | Description                 |
| ------ | --------------------------- |
| `1000` | Standard plan               |
| `3000` | Middle-tier plan            |
| `6000` | Maximum plan                |
| `null` | Unlimited (no restrictions) |

***

## Device registration

When adding a subscription, the app registers the device to track active connections and push notifications.

### Registration data

| Field                    | Type      | Description                                                        |
| ------------------------ | --------- | ------------------------------------------------------------------ |
| `hwid`                   | string    | Device hardware identifier ([more details](/docs/docs-en/hwid.md)) |
| `hwidHash`               | string    | SHA-256 hash of the HWID (for server-side limit checks)            |
| `uid`                    | string    | Anonymous device UID                                               |
| `platform`               | string    | `"android"`, `"ios"`, `"linux"`, `"windows"`, `"macos"`            |
| `appVersion`             | string    | App version                                                        |
| `osVersion`              | string    | OS version                                                         |
| `locale`                 | string    | Device language                                                    |
| `subscriptionDomainHash` | string    | SHA-256 hash of the subscription domain                            |
| `fcmToken`               | string    | Push token for notifications (FCM / APNs)                          |
| `lastActive`             | timestamp | Time of last activity                                              |

> Field `hwidHash` is added during registration and used by the server to check the device limit without knowing the original HWID.

### Subscription sync

The server can set the flag `subscriptionNeedsSync = true` on the device. When this flag is detected, the app:

1. Reads the field `subscriptionNewDomain` from the device document
2. Updates the subscription URL to the new domain
3. Resets the flag `subscriptionNeedsSync`

This allows the provider to migrate users to a new domain when the old one is blocked.

***

## Caching

* **In-memory cache:** configuration is stored in memory while the app is running
* **Disk cache:** saved in SecureStorage for offline access
* **Fallback:** on error `503` the cached configuration is used
* **Cleanup:** configuration is removed from the cache if the domain is no longer premium

***

## Certificate Pinning

Requests to the Premium API on Android and Desktop are protected by certificate pinning (SHA-256 TLS certificate pins). This prevents MITM attacks on the communication channel with the API.
