# Premium API

### Overview

When adding a subscription, the app extracts the domain from the URL and requests the provider configuration via an encrypted API. The domain is never transmitted 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 verify the device limit |

The hash is computed 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 has Premium active            |
| `deviceLimitExceeded` | boolean | Provider device limit 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 using AES-256-GCM. The encryption key is derived from the domain — only a 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 | Force HWID sending (the user cannot disable it) |
| `expiryNotifications`   | boolean | Local notifications about subscription expiry   |
| `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 resolving 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`) |
| `serverAddressResolveDnsIp`     | string  | DoH server IP (used until its domain is resolved)                   |

**Lite Mode**

A simplified interface with links to the bot, channel, support, and (optionally) premium. More about icons — icon-presets.md.

| Field            | Type    | Description                                            |
| ---------------- | ------- | ------------------------------------------------------ |
| `liteMode`       | boolean | Enable simplified mode                                 |
| `liteBigButton`  | boolean | Large connect button                                   |
| `botUrl`         | string? | Telegram bot link                                      |
| `channelUrl`     | string? | Telegram channel link                                  |
| `supportUrl`     | string? | Support link                                           |
| `botIconKey`     | string? | Bot icon key from the preset set (see icon-presets.md) |
| `channelIconKey` | string? | Channel icon key                                       |
| `supportIconKey` | string? | Support icon key                                       |

> `null` or 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 it matches `targetSegment.hwid`).

Details — admin-hwids.md.

| Field        | Type       | Description                            |
| ------------ | ---------- | -------------------------------------- |
| `adminHwids` | `string[]` | List of device HWIDs with admin rights |

**Subscription banner**

A red/custom-colored banner inside the subscription card. Used for upsell / expiry 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 choice in the app settings while the subscription is active.

| Field                  | Type      | Description                                                                                            |
| ---------------------- | --------- | ------------------------------------------------------------------------------------------------------ |
| `forceConnectionStyle` | `string?` | Connection button style: `"classic"` (large round button) or `"compact"` (narrow 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: `tcp`, `proxy_head`, `proxy_get`, `icmp`     |
| `defaultSortOrder`    | string?  | Server sorting: `none`, `ping`, `name`                  |
| `pingOnUpdate`        | boolean? | Auto-ping all servers after the subscription is updated |

**Content**

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

#### Custom theme (`theme`)

An 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 set, 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 the 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 limit on the number of devices. The limit is stored in the `maxDevices` field of the provider document in Firestore.

#### 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 this provider's domains
3. If the device **is already registered** — it always receives premium (existing devices are not blocked)
4. If the device **is not registered** — the server counts the provider's total number of devices:
   * If `totalDevices < maxDevices` — premium is granted
   * If `totalDevices >= maxDevices` — returns `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 in Firebase (but without premium)

#### Plans

| Limit  | Description                 |
| ------ | --------------------------- |
| `1000` | Standard plan               |
| `3000` | Mid-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    | Hardware device identifier (more details)               |
| `hwidHash`               | string    | SHA-256 hash of the HWID (for server-side limit checks) |
| `uid`                    | string    | Firebase anonymous UID                                  |
| `platform`               | string    | `"android"`, `"ios"`, `"linux"`, `"windows"`            |
| `appVersion`             | string    | App version                                             |
| `osVersion`              | string    | OS version                                              |
| `locale`                 | string    | Device language                                         |
| `subscriptionDomainHash` | string    | SHA-256 hash of the subscription domain                 |
| `fcmToken`               | string    | Firebase Cloud Messaging token                          |
| `lastActive`             | timestamp | Last active time                                        |

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

#### Subscription synchronization

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:** the configuration is stored in memory while the app is running
* **Disk cache:** stored in SecureStorage for offline access
* **Fallback:** on error `503` the cached configuration is used
* **Cleanup:** the 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.

***

#### Firestore index

To make limit checks work correctly, a composite index is required:

* **Collection:** `devices`
* **Fields:** `hwidHash` (ASC), `subscriptionDomainHash` (ASC)\`


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://incy.gitbook.io/docs/docs-en/premium-api.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
