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

# Реферальная система

Набор методов для работы с реферальной программой бота: выдача реферальных кодов, статистика по приглашённым пользователям, баланс вознаграждений и выплаты.

> Во всех методах под «пользователем» понимается Telegram-пользователь вашего бота, и он передаётся как `user_id` (это Telegram ID, а не ваш внутренний ID).

### Как это работает в двух словах

1. Вы создаёте реферальный код для пользователя (`create-code`) и даёте ему ссылку вида `https://t.me/your_bot?start=КОД`.
2. Когда по этой ссылке переходит новый человек, он автоматически засчитывается как приглашённый (referral) этому пользователю.
3. Если вы используете свои собственные коды (не выданные этим API) — сообщите систему, кому какой код принадлежит, через `set-code-owners`.
4. Узнать, сколько человек привёл пользователь и сколько ему причитается — методы `referrals` и `balance`.
5. Когда пользователь хочет вывести вознаграждение — вызовите `payout`.
6. Если начисление оказалось ошибочным — его можно отменить через `reverse-accrual`.

{% hint style="info" %}
Реферальная система подробней описана в [этом разделе](/ru/app/referral-system.md)
{% endhint %}

### 1. Создать реферальный код

`POST /v1/referral/create-code` (также поддерживается `GET`)

Создаёт новый уникальный реферальный код для пользователя и сразу отдаёт готовую ссылку-приглашение. Один и тот же пользователь может иметь сколько угодно кодов — каждый вызов создаёт **новый** код, старые при этом не отзываются.

#### Параметры

<table><thead><tr><th width="125.87109375">Параметр</th><th width="94.77734375">Тип</th><th width="148.21484375">Обязательный</th><th>Описание</th></tr></thead><tbody><tr><td><code>user_id</code></td><td>int</td><td>да</td><td>Telegram ID пользователя, для которого создаётся код (реферер)</td></tr></tbody></table>

#### Пример вызова

{% code overflow="wrap" %}

```http
GET /v1/referral/create-code?user_id=123456789
```

{% endcode %}

#### Ответ

{% code overflow="wrap" lineNumbers="true" %}

```json
{  
    "ok": true,  
    "data": {    
        "code": "kT7xQm2p",
        "link": "https://t.me/your_bot?start=kT7xQm2p"  
    }
}
```

{% endcode %}

<table><thead><tr><th width="85.92578125">Поле</th><th width="108.4375">Тип</th><th>Описание</th></tr></thead><tbody><tr><td><code>code</code></td><td>string</td><td>Сам код, 8 символов. Цифры и буквы без похожих друг на друга символов (нет <code>0</code>, <code>O</code>, <code>1</code>, <code>l</code>, <code>I</code>), чтобы код было легко прочитать и ввести вручную</td></tr><tr><td><code>link</code></td><td>string|null</td><td>Готовая ссылка-приглашение вида <code>https://t.me/&#x3C;бот>?start=&#x3C;код></code>. Будет <code>null</code>, если у бота не определён username</td></tr></tbody></table>

#### Возможные ошибки

<table><thead><tr><th width="77.921875">Код</th><th width="303.03125">Когда возникает</th><th>Как исправить</th></tr></thead><tbody><tr><td><code>400</code></td><td>Не передан <code>user_id</code>, или он не является числом</td><td>Передайте корректный Telegram ID</td></tr><tr><td><code>401</code></td><td>API-ключ не передан или недействителен</td><td>Проверьте заголовок <code>Api-Key</code> / параметр <code>api-key</code></td></tr><tr><td><code>500</code></td><td>Внутренняя ошибка сервера</td><td>Повторите запрос позже; если повторяется — обратитесь в поддержку</td></tr></tbody></table>

***

### 2. Привязать владельца к коду (set-code-owners)

2\. Привязать владельца к коду (set-code-owners)

`POST /v1/referral/set-code-owners`

Этот метод нужен только в одном случае: если ваш бот **сам** генерирует или раздаёт реферальные коды (не через `create-code`), а нашей системе об этом неизвестно. Метод сообщает: «вот этот код принадлежит вот этому пользователю».

Дополнительный эффект — **бэкофилл**: если по этому коду уже были переходы до того, как вы сообщили владельца, все они задним числом будут засчитаны этому пользователю как приглашённые.

Можно передать сразу несколько кодов в одном запросе.

{% hint style="info" %}
Рекомендуем, по возможности передавать кода заранее, это положительно скажется при построении возможных отчетов в будущем
{% endhint %}

#### Параметры (тело запроса — массив объектов)

<table><thead><tr><th width="137.59765625">Параметр</th><th width="107.1875">Тип</th><th width="153.36328125">Обязательный</th><th>Описание</th></tr></thead><tbody><tr><td><code>code</code></td><td>string</td><td>да</td><td>Реферальный код</td></tr><tr><td><code>user_id</code></td><td>int</td><td>да</td><td>Telegram ID владельца кода (реферера)</td></tr></tbody></table>

#### Пример вызова

```
POST /v1/referral/set-code-owners
```

{% code overflow="wrap" lineNumbers="true" %}

```json
 [
  { 
    "code": "ABC123", 
    "user_id": 123456789 
  },
  { 
    "code": "XYZ789",
    "user_id": 987654321 
  }
]
```

{% endcode %}

#### Ответ

{% code overflow="wrap" lineNumbers="true" %}

```json
{  
    "ok": true,  
    "data": {    
        "results": [      
            { 
                "code": "ABC123", 
                "owner_id": 123456789, 
                "created": false, 
                "backfilled": 5 
            },  
            { 
                "code": "XYZ789", 
                "owner_id": 987654321, 
                "created": true, 
                "backfilled": 0 
            }
        ] 
    }
}
```

{% endcode %}

<table><thead><tr><th width="144.9609375">Поле</th><th width="109.48828125">Тип</th><th>Описание</th></tr></thead><tbody><tr><td><code>code</code></td><td>string</td><td>Код, который был обработан</td></tr><tr><td><code>owner_id</code></td><td>int</td><td>Telegram ID, который был привязан к коду</td></tr><tr><td><code>created</code></td><td>bool</td><td><code>true</code> — кода раньше не существовало, он был зарегистрирован прямо сейчас. <code>false</code> — код уже существовал, и ему просто назначили владельца</td></tr><tr><td><code>backfilled</code></td><td>int</td><td>Сколько прошлых переходов по этому коду задним числом получили этого владельца как реферера</td></tr></tbody></table>

#### Возможные ошибки

<table><thead><tr><th width="100.359375">Код</th><th>Когда возникает</th><th>Как исправить</th></tr></thead><tbody><tr><td><code>400</code></td><td>Тело запроса не массив или пустой массив</td><td>Передайте непустой JSON-массив объектов</td></tr><tr><td><code>400</code></td><td>В одном из объектов отсутствует <code>code</code> или <code>user_id</code> (или <code>user_id</code> равен 0)</td><td>Проверьте, что в каждом объекте заполнены оба поля</td></tr><tr><td><code>401</code></td><td>API-ключ не передан или недействителен</td><td>Проверьте API-ключ</td></tr><tr><td><code>500</code></td><td>Внутренняя ошибка сервера</td><td>Повторите запрос позже</td></tr></tbody></table>

***

### 3. Список кодов пользователя

`GET /v1/referral/codes`

Возвращает все реферальные коды, выданные конкретному пользователю через `create-code`, плюс общее число приглашённых им людей (по всем его кодам сразу).

#### Параметры

<table><thead><tr><th width="161.140625">Параметр</th><th width="113.3203125">Тип</th><th width="162.31640625">Обязательный</th><th>Описание</th></tr></thead><tbody><tr><td><code>user_id</code></td><td>int</td><td>да</td><td>Telegram ID пользователя</td></tr></tbody></table>

#### Пример вызова

{% code overflow="wrap" %}

```http
GET /v1/referral/codes?user_id=123456789
```

{% endcode %}

#### Ответ

{% code overflow="wrap" lineNumbers="true" %}

```json
{
  "ok": true,
  "data": {
    "total_referrals": 12,
    "codes": [
      {
        "code": "kT7xQm2p",
        "link": "https://t.me/your_bot?start=kT7xQm2p",
        "date_create": "2026-06-10 14:32:01"
      }
    ]
  }
}
```

{% endcode %}

<table><thead><tr><th width="213.5390625">Поле</th><th width="155.05078125">Тип</th><th>Описание</th></tr></thead><tbody><tr><td><code>total_referrals</code></td><td>int</td><td>Сколько всего людей привёл этот пользователь (по всем его кодам)</td></tr><tr><td><code>codes</code></td><td>array</td><td>Список кодов, от новых к старым</td></tr><tr><td><code>codes[].code</code></td><td>string</td><td>Код</td></tr><tr><td><code>codes[].link</code></td><td>string|null</td><td>Готовая ссылка-приглашение</td></tr><tr><td><code>codes[].date_create</code></td><td>string</td><td>Дата и время создания кода (<code>YYYY-MM-DD HH:MM:SS</code>, UTC)</td></tr></tbody></table>

> Метод показывает только коды, созданные через `create-code`. Коды, привязанные через `set-code-owners` без предварительного создания, в этом списке не появятся.

#### Возможные ошибки

<table><thead><tr><th width="114.76953125">Код</th><th>Когда возникает</th><th>Как исправить</th></tr></thead><tbody><tr><td><code>400</code></td><td>Не передан <code>user_id</code>, или он не является числом</td><td>Передайте корректный Telegram ID</td></tr><tr><td><code>401</code></td><td>API-ключ не передан или недействителен</td><td>Проверьте API-ключ</td></tr><tr><td><code>500</code></td><td>Внутренняя ошибка сервера</td><td>Повторите запрос позже</td></tr></tbody></table>

***

### 4. Список приглашённых пользователей

`GET /v1/referral/referrals`

Возвращает список людей, которых пользователь привёл по своим реферальным кодам (по всем кодам сразу), с датой перехода каждого.

#### Параметры

<table><thead><tr><th width="118.6015625">Параметр</th><th width="83.36328125">Тип</th><th width="156.30859375">Обязательный</th><th width="154.06640625">По умолчанию</th><th>Описание</th></tr></thead><tbody><tr><td><code>user_id</code></td><td>int</td><td>да</td><td>—</td><td>Telegram ID реферера</td></tr><tr><td><code>limit</code></td><td>int</td><td>нет</td><td>50</td><td>Сколько записей вернуть за раз (от 1 до 200; значения вне диапазона будут автоматически округлены до границ)</td></tr><tr><td><code>offset</code></td><td>int</td><td>нет</td><td>0</td><td>Сколько записей пропустить с начала списка — используется для постраничной выгрузки</td></tr></tbody></table>

#### Пример вызова

{% code overflow="wrap" %}

```http
GET /v1/referral/referrals?user_id=123456789&limit=20&offset=0
```

{% endcode %}

#### Ответ

{% code overflow="wrap" lineNumbers="true" %}

```json
{
  "ok": true,
  "data": {
    "referrals": [
      { "referral_id": 555111222, "date": "2026-06-15 09:12:44" }
    ]
  }
}
```

{% endcode %}

<table><thead><tr><th width="250">Поле</th><th width="119.75">Тип</th><th>Описание</th></tr></thead><tbody><tr><td><code>referrals</code></td><td>array</td><td>Список приглашённых, от новых к старым</td></tr><tr><td><code>referrals[].referral_id</code></td><td>int</td><td>Telegram ID приглашённого пользователя</td></tr><tr><td><code>referrals[].date</code></td><td>string</td><td>Дата и время перехода по реферальной ссылке (UTC)</td></tr></tbody></table>

#### Возможные ошибки

<table><thead><tr><th width="131.79296875">Код</th><th>Когда возникает</th><th>Как исправить</th></tr></thead><tbody><tr><td><code>400</code></td><td>Не передан <code>user_id</code>, или он не является числом</td><td>Передайте корректный Telegram ID</td></tr><tr><td><code>401</code></td><td>API-ключ не передан или недействителен</td><td>Проверьте API-ключ</td></tr><tr><td><code>500</code></td><td>Внутренняя ошибка сервера</td><td>Повторите запрос позже</td></tr></tbody></table>

***

### 5. Баланс вознаграждений

`GET /v1/referral/balance`

Показывает, сколько денег (или другой валюты вознаграждения) накопил пользователь за привлечённых им людей, в разбивке по статусам.

#### Параметры

<table><thead><tr><th width="147.6328125">Параметр</th><th width="99.44921875">Тип</th><th width="161.9375">Обязательный</th><th>Описание</th></tr></thead><tbody><tr><td><code>user_id</code></td><td>int</td><td>да</td><td>Telegram ID реферера</td></tr></tbody></table>

#### Пример вызова

{% code overflow="wrap" %}

```http
GET /v1/referral/balance?user_id=123456789
```

{% endcode %}

#### Ответ

{% code overflow="wrap" lineNumbers="true" %}

```json
{
  "ok": true,
  "data": {
    "balance": [
      {
        "unit_id": 1,
        "available": 150.5,
        "reserved": 20,
        "paid": 300,
        "on_hold": 10
      }
    ]
  }
}
```

{% endcode %}

<table><thead><tr><th width="223.94921875">Поле</th><th width="120.7734375">Тип</th><th>Описание</th></tr></thead><tbody><tr><td><code>balance</code></td><td>array</td><td>Одна строка на каждую валюту (<code>unit_id</code>), в которой у пользователя есть какие-либо начисления</td></tr><tr><td><code>balance[].unit_id</code></td><td>int</td><td>Идентификатор валюты/единицы вознаграждения</td></tr><tr><td><code>balance[].available</code></td><td>number</td><td>Доступно к выводу прямо сейчас</td></tr><tr><td><code>balance[].reserved</code></td><td>number</td><td>Зарезервировано — уже включено в созданную, но ещё не обработанную выплату</td></tr><tr><td><code>balance[].paid</code></td><td>number</td><td>Уже выплачено пользователю ранее</td></tr><tr><td><code>balance[].on_hold</code></td><td>number</td><td>На удержании — начислено, но ещё не подтверждено к выплате (например, ожидает проверки)</td></tr></tbody></table>

#### Возможные ошибки

<table><thead><tr><th width="125.20703125">Код</th><th width="297.7265625">Когда возникает</th><th>Как исправить</th></tr></thead><tbody><tr><td><code>400</code></td><td>Не передан <code>user_id</code>, или он не является числом</td><td>Передайте корректный Telegram ID</td></tr><tr><td><code>401</code></td><td>API-ключ не передан или недействителен</td><td>Проверьте API-ключ</td></tr><tr><td><code>500</code></td><td>Внутренняя ошибка сервера</td><td>Повторите запрос позже</td></tr></tbody></table>

***

### 6. Запросить выплату

`POST /v1/referral/payout`

Запускает выплату всего доступного (`available`) баланса пользователя. Метод только **создаёт заявку на выплату** — сама выплата (например, отправка вебхука с уведомлением о начислении) выполняется системой отдельно, по расписанию.

> Важно: метод не проверяет минимальный порог для вывода и не спрашивает разрешения — если вы его вызвали, заявка создаётся сразу на всю доступную сумму. Решение, когда вызывать этот метод (например, только если у пользователя накопилось от 100 ₸), остаётся на стороне вашего бота.

#### Параметры

<table><thead><tr><th width="115.65234375">Параметр</th><th width="85.37109375">Тип</th><th width="151.8046875">Обязательный</th><th width="149.7734375">По умолчанию</th><th>Описание</th></tr></thead><tbody><tr><td><code>user_id</code></td><td>int</td><td>да</td><td>—</td><td>Telegram ID реферера</td></tr><tr><td><code>unit_id</code></td><td>int</td><td>нет</td><td>не задано</td><td>Валюта, по которой нужно вывести средства. Если не передать — выводится весь доступный баланс по любой валюте, где он есть</td></tr><tr><td><code>method</code></td><td>string</td><td>нет</td><td><code>webhook</code></td><td>Способ исполнения выплаты: <code>webhook</code> — система сама отправит уведомление о выплате на ваш вебхук; <code>manual</code> — выплата будет отмечена как требующая ручной обработки администратором</td></tr></tbody></table>

#### Пример вызова

`POST /v1/referral/payout`

{% code overflow="wrap" %}

```json
{ "user_id": 123456789, "unit_id": 1, "method": "webhook" }
```

{% endcode %}

#### Ответ

{% code overflow="wrap" lineNumbers="true" %}

```json
{
  "ok": true,
  "data": { "payout_id": 4821 }
}
```

{% endcode %}

<table><thead><tr><th width="155.09765625">Поле</th><th width="113.6015625">Тип</th><th>Описание</th></tr></thead><tbody><tr><td><code>payout_id</code></td><td>int</td><td>Идентификатор созданной заявки на выплату. Используйте его, чтобы найти эту выплату в <code>GET /v1/referral/payouts</code></td></tr></tbody></table>

#### Возможные ошибки

<table><thead><tr><th width="92.84375">Код</th><th>Когда возникает</th><th>Как исправить</th></tr></thead><tbody><tr><td><code>400</code></td><td>Не передан <code>user_id</code>, или он не является числом</td><td>Передайте корректный Telegram ID</td></tr><tr><td><code>400</code></td><td>У пользователя нет доступного баланса для вывода (<code>no available balance</code>)</td><td>Перед вызовом проверяйте баланс через <code>GET /v1/referral/balance</code> — выводить нечего, если <code>available</code> равен нулю</td></tr><tr><td><code>401</code></td><td>API-ключ не передан или недействителен</td><td>Проверьте API-ключ</td></tr><tr><td><code>500</code></td><td>Внутренняя ошибка сервера</td><td>Повторите запрос позже</td></tr></tbody></table>

***

### 7. История выплат

`GET /v1/referral/payouts`

Возвращает список всех заявок на выплату пользователя — как уже обработанных, так и ещё в процессе.

#### Параметры

| Параметр  | Тип | Обязательный | По умолчанию | Описание                                     |
| --------- | --- | ------------ | ------------ | -------------------------------------------- |
| `user_id` | int | да           | —            | Telegram ID реферера                         |
| `limit`   | int | нет          | 50           | Сколько записей вернуть за раз (от 1 до 200) |
| `offset`  | int | нет          | 0            | Сколько записей пропустить с начала списка   |

#### Пример вызова

{% code overflow="wrap" %}

```
GET /v1/referral/payouts?user_id=123456789&limit=20&offset=0&api-key=YOUR_API_KEY
```

{% endcode %}

#### Ответ

{% code overflow="wrap" %}

```json
{
  "ok": true,
  "data": {
    "payouts": [
      {
        "payout_id": 4821,
        "amount": 150.5,
        "unit_id": 1,
        "status": 2,
        "method": "webhook",
        "initiated_by": "api",
        "requested_at": "2026-06-18 10:00:00",
        "processed_at": "2026-06-18 10:05:32"
      }
    ]
  }
}
```

{% endcode %}

| Поле                     | Тип          | Описание                                                                                                               |
| ------------------------ | ------------ | ---------------------------------------------------------------------------------------------------------------------- |
| `payouts`                | array        | Список заявок на выплату, от новых к старым                                                                            |
| `payouts[].payout_id`    | int          | Идентификатор заявки                                                                                                   |
| `payouts[].amount`       | number       | Сумма выплаты                                                                                                          |
| `payouts[].unit_id`      | int          | Валюта/единица выплаты                                                                                                 |
| `payouts[].status`       | int          | Числовой статус заявки (этап обработки внутри системы)                                                                 |
| `payouts[].method`       | string       | Способ исполнения: `webhook` или `manual`                                                                              |
| `payouts[].initiated_by` | string       | Кто инициировал заявку: `api` (через этот метод), `admin` (вручную в админ-панели) или `auto` (автоматически системой) |
| `payouts[].requested_at` | string       | Дата и время создания заявки (UTC)                                                                                     |
| `payouts[].processed_at` | string\|null | Дата и время фактической обработки заявки. `null`, если ещё не обработана                                              |

#### Возможные ошибки

| Код   | Когда возникает                                 | Как исправить                    |
| ----- | ----------------------------------------------- | -------------------------------- |
| `400` | Не передан `user_id`, или он не является числом | Передайте корректный Telegram ID |
| `401` | API-ключ не передан или недействителен          | Проверьте API-ключ               |
| `500` | Внутренняя ошибка сервера                       | Повторите запрос позже           |

### 8. Аннулировать начисление

`POST /v1/referral/reverse-accrual`

Отменяет одно конкретное начисление вознаграждения по его идентификатору (`accrual_id`). Используется, если начисление было сделано по ошибке (например, отменённый/возвращённый платёж приглашённого пользователя).

Поведение зависит от текущего состояния начисления:

* если начисление ещё не выплачено — оно просто помечается как отменённое, и сумма исчезает из баланса пользователя;
* если начисление уже выплачено — создаётся компенсирующая запись (как бы «минус» на ту же сумму), а само начисление помечается как аннулированное.

#### Параметры (тело запроса)

<table><thead><tr><th width="145.31640625">Параметр</th><th width="108.0390625">Тип</th><th width="163.51171875">Обязательный</th><th>Описание</th></tr></thead><tbody><tr><td><code>accrual_id</code></td><td>int</td><td>да</td><td>Идентификатор начисления, которое нужно аннулировать</td></tr><tr><td><code>reason</code></td><td>string</td><td>нет</td><td>Причина аннулирования — сохраняется для истории, ни на что не влияет</td></tr></tbody></table>

#### Пример вызова

{% code overflow="wrap" %}

```json
{ "accrual_id": 99812, "reason": "Платёж пользователя был возвращён" }
```

{% endcode %}

#### Ответ

{% code overflow="wrap" %}

```json
{
  "ok": true,
  "data": {}
}
```

{% endcode %}

Метод не возвращает дополнительных данных — успешный ответ (`ok: true`) означает, что начисление аннулировано.

#### Возможные ошибки

<table><thead><tr><th width="110.32421875">Код</th><th>Когда возникает</th><th>Как исправить</th></tr></thead><tbody><tr><td><code>400</code></td><td>Не передан <code>accrual_id</code></td><td>Передайте корректный идентификатор начисления</td></tr><tr><td><code>404</code></td><td>Начисление с таким <code>accrual_id</code> не найдено (или принадлежит другому боту)</td><td>Проверьте, что <code>accrual_id</code> верный и относится к вашему боту</td></tr><tr><td><code>409</code></td><td>Начисление уже находится в состоянии, не допускающем аннулирование (например, уже отменено ранее)</td><td>Повторное аннулирование не требуется — начисление уже неактивно</td></tr><tr><td><code>401</code></td><td>API-ключ не передан или недействителен</td><td>Проверьте API-ключ</td></tr><tr><td><code>500</code></td><td>Внутренняя ошибка сервера</td><td>Повторите запрос позже</td></tr></tbody></table>

***

### 9. Пометить пользователя как реферала (mark-referral)

`POST /v1/referral/mark-referral`

Напрямую помечает пользователя как реферала — без перехода по реферальной ссылке. Используйте, если вы сами знаете (из своих данных), что пользователь пришёл от конкретного реферера, и хотите просто сообщить об этом системе.

Укажите реферера **одним из двух способов**:

* `code` — уже существующий реферальный код (выданный через `create-code` или сообщённый через `set-code-owners`);
* `referrer_user_id` — Telegram ID реферера напрямую. Если у него ещё нет своего кода, он будет создан автоматически.

Передавать оба параметра одновременно нельзя.

Метод идемпотентен: если у пользователя уже есть привязанный реферер (в том числе по более раннему переходу по ссылке), повторный вызов ничего не меняет — в ответе вернётся уже существующий реферер.

#### Параметры (тело запроса)

<table><thead><tr><th width="180.45703125">Параметр</th><th width="85.734375">Тип</th><th width="218.0625">Обязательный</th><th>Описание</th></tr></thead><tbody><tr><td><code>referral_user_id</code></td><td>int</td><td>да</td><td>Telegram ID пользователя, которого нужно пометить рефералом</td></tr><tr><td><code>code</code></td><td>string</td><td>один из двух (<code>code</code> или <code>referrer_user_id</code>)</td><td>Существующий реферальный код реферера</td></tr><tr><td><code>referrer_user_id</code></td><td>int</td><td>один из двух (<code>code</code> или <code>referrer_user_id</code>)</td><td>Telegram ID реферера</td></tr></tbody></table>

#### Пример вызова

{% code overflow="wrap" %}

```json
{ "referral_user_id": 555111222, "referrer_user_id": 123456789 }
```

{% endcode %}

#### Ответ

{% code overflow="wrap" %}

```json
{
  "ok": true,
  "data": {
    "referral_id": 555111222,
    "referrer_id": 123456789,
    "code": "kT7xQm2p",
    "already_attributed": false
  }
}
```

{% endcode %}

<table><thead><tr><th width="207.23046875">Поле</th><th width="123.703125">Тип</th><th>Описание</th></tr></thead><tbody><tr><td><code>referral_id</code></td><td>int</td><td>Telegram ID помеченного реферала (тот же <code>referral_user_id</code>)</td></tr><tr><td><code>referrer_id</code></td><td>int|null</td><td>Telegram ID реферера. <code>null</code> возможен только при привязке по <code>code</code>, если у этого кода ещё нет известного владельца</td></tr><tr><td><code>code</code></td><td>string|null</td><td>Код, через который оформлена привязка. <code>null</code>, если привязка делалась по <code>referrer_user_id</code> и у реферера уже был свой код (использован он, но его строка не возвращается)</td></tr><tr><td><code>already_attributed</code></td><td>bool</td><td><code>true</code> — у пользователя уже был реферер (текущий запрос ничего не изменил, вернули то, что уже есть). <code>false</code> — привязка создана этим вызовом</td></tr></tbody></table>

#### Возможные ошибки

<table><thead><tr><th width="78.59765625">Код</th><th>Когда возникает</th><th>Как исправить</th></tr></thead><tbody><tr><td><code>400</code></td><td>Не передан <code>referral_user_id</code>, либо переданы и <code>code</code>, и <code>referrer_user_id</code> одновременно, либо не передан ни один из них</td><td>Передайте <code>referral_user_id</code> и ровно один из <code>code</code>/<code>referrer_user_id</code></td></tr><tr><td><code>400</code></td><td>Пользователь пытается стать рефералом самого себя</td><td>Проверьте, что <code>referral_user_id</code> и реферер — разные пользователи</td></tr><tr><td><code>404</code></td><td>Указанный <code>code</code> не найден</td><td>Проверьте код, либо используйте <code>referrer_user_id</code></td></tr><tr><td><code>401</code></td><td>API-ключ не передан или недействителен</td><td>Проверьте API-ключ</td></tr><tr><td><code>500</code></td><td>Внутренняя ошибка сервера</td><td>Повторите запрос позже</td></tr></tbody></table>

***

### 10. Список начислений

`GET /v1/referral/accruals`

Возвращает доходную часть истории баланса пользователя — список отдельных начислений вознаграждения (по каждому приведённому пользователю и событию отдельная строка). Списания (выплаты) возвращает `GET /v1/referral/payouts` — вместе эти два метода дают полную ленту движений по балансу.

Аннулированные и отменённые начисления (см. `reverse-accrual`) в список не попадают — это леджер «живого» баланса, а не полный аудит.

#### Параметры

<table><thead><tr><th width="122.765625">Параметр</th><th width="87.15234375">Тип</th><th width="148.65234375">Обязательный</th><th width="148.828125">По умолчанию</th><th>Описание</th></tr></thead><tbody><tr><td><code>user_id</code></td><td>int</td><td>да</td><td>—</td><td>Telegram ID реферера</td></tr><tr><td><code>limit</code></td><td>int</td><td>нет</td><td>50</td><td>Сколько записей вернуть за раз (от 1 до 200)</td></tr><tr><td><code>offset</code></td><td>int</td><td>нет</td><td>0</td><td>Сколько записей пропустить с начала списка</td></tr></tbody></table>

#### Пример вызова

{% code overflow="wrap" %}

```http
GET /v1/referral/accruals?user_id=123456789&limit=20&offset=0
```

{% endcode %}

#### Ответ

{% code overflow="wrap" %}

```json
{
  "ok": true,
  "data": {
    "accruals": [
      {
        "id": 99812,
        "date": "2026-06-15 09:13:01",
        "referral_id": 555111222,
        "amount": 30.10,
        "unit_id": 1,
        "status": 1,
        "available_at": "2026-07-15 09:13:01"
      }
    ]
  }
}
```

{% endcode %}

<table><thead><tr><th width="225.84765625">Поле</th><th width="133.62109375">Тип</th><th>Описание</th></tr></thead><tbody><tr><td><code>accruals</code></td><td>array</td><td>Список начислений, от новых к старым</td></tr><tr><td><code>accruals[].id</code></td><td>int</td><td>Идентификатор начисления (используется как <code>accrual_id</code> в <code>reverse-accrual</code>)</td></tr><tr><td><code>accruals[].date</code></td><td>string</td><td>Дата и время создания начисления (UTC)</td></tr><tr><td><code>accruals[].referral_id</code></td><td>int</td><td>Telegram ID приглашённого пользователя, за чьё действие начислено вознаграждение</td></tr><tr><td><code>accruals[].amount</code></td><td>number</td><td>Сумма начисления (может быть отрицательной — компенсирующая запись после аннулирования)</td></tr><tr><td><code>accruals[].unit_id</code></td><td>int|null</td><td>Валюта/единица вознаграждения</td></tr><tr><td><code>accruals[].status</code></td><td>int</td><td><code>0</code> — на выдержке, <code>1</code> — подтверждено (доступно к выводу), <code>2</code> — выплачено</td></tr><tr><td><code>accruals[].available_at</code></td><td>string|null</td><td>Когда начисление станет доступно к выводу (выдержка). <code>null</code>, если выдержка не применяется или уже прошла</td></tr></tbody></table>

#### Возможные ошибки

<table><thead><tr><th width="106.95703125">Код</th><th width="302.6328125">Когда возникает</th><th>Как исправить</th></tr></thead><tbody><tr><td><code>400</code></td><td>Не передан <code>user_id</code>, или он не является числом</td><td>Передайте корректный Telegram ID</td></tr><tr><td><code>401</code></td><td>API-ключ не передан или недействителен</td><td>Проверьте API-ключ</td></tr><tr><td><code>500</code></td><td>Внутренняя ошибка сервера</td><td>Повторите запрос позже</td></tr></tbody></table>

***

### 11. Действующая ставка вознаграждения

`GET /v1/referral/rates`

Показывает, на каких условиях пользователь получает вознаграждение за приглашённых — по каждому активному правилу программы отдельно (если у бота несколько правил, например за оплату и за подписку). Полезно, чтобы показать пользователю «вы получаете 10% с каждой оплаты» в своём интерфейсе.

Если для пользователя ранее уже было хотя бы одно начисление по правилу — отдаётся его зафиксированная ставка (`locked: true`), даже если с тех пор общая ставка программы изменилась. Если начислений ещё не было — отдаётся текущая ставка правила (`locked: false`); она будет зафиксирована за пользователем автоматически при первом подходящем начислении.

#### Параметры

<table><thead><tr><th width="130.578125">Параметр</th><th width="88.32421875">Тип</th><th width="165.09375">Обязательный</th><th>Описание</th></tr></thead><tbody><tr><td><code>user_id</code></td><td>int</td><td>да</td><td>Telegram ID реферера</td></tr></tbody></table>

#### Пример вызова

{% code overflow="wrap" %}

```http
GET /v1/referral/rates?user_id=123456789
```

{% endcode %}

#### Ответ

{% code overflow="wrap" %}

```json
{
  "ok": true,
  "data": {
    "rates": [
      {
        "rule_id": 7,
        "rule_name": "Процент с оплат",
        "trigger_event_id": 12,
        "reward_type": "percent",
        "reward_amount": null,
        "reward_percent": "10.0000",
        "unit_id": 1,
        "locked": true,
        "source": "auto"
      }
    ]
  }
}
```

{% endcode %}

<table><thead><tr><th width="233.61328125">Поле</th><th width="132.515625">Тип</th><th>Описание</th></tr></thead><tbody><tr><td><code>rates</code></td><td>array</td><td>Одна строка на каждое активное правило программы у бота</td></tr><tr><td><code>rates[].rule_id</code></td><td>int</td><td>Идентификатор правила</td></tr><tr><td><code>rates[].rule_name</code></td><td>string|null</td><td>Название правила (как задано в админке)</td></tr><tr><td><code>rates[].trigger_event_id</code></td><td>int</td><td>Тип события, за которое начисляется вознаграждение по этому правилу</td></tr><tr><td><code>rates[].reward_type</code></td><td>string</td><td><code>fixed</code> — фиксированная сумма, <code>percent</code> — процент от суммы события, <code>external</code> — вознаграждение выдаётся на вашей стороне</td></tr><tr><td><code>rates[].reward_amount</code></td><td>number|null</td><td>Сумма вознаграждения для <code>fixed</code></td></tr><tr><td><code>rates[].reward_percent</code></td><td>number|null</td><td>Процент вознаграждения для <code>percent</code></td></tr><tr><td><code>rates[].unit_id</code></td><td>int|null</td><td>Валюта/единица вознаграждения</td></tr><tr><td><code>rates[].locked</code></td><td>bool</td><td><code>true</code> — ставка зафиксирована конкретно за этим пользователем (не изменится при правке общей ставки программы); <code>false</code> — действует общая текущая ставка программы</td></tr><tr><td><code>rates[].source</code></td><td>string|null</td><td><code>auto</code> — зафиксирована автоматически при первом начислении; <code>manual</code> — индивидуально задана администратором; <code>null</code>, если <code>locked</code> равно <code>false</code></td></tr></tbody></table>

#### Возможные ошибки

<table><thead><tr><th width="111.2890625">Код</th><th>Когда возникает</th><th>Как исправить</th></tr></thead><tbody><tr><td><code>400</code></td><td>Не передан <code>user_id</code>, или он не является числом</td><td>Передайте корректный Telegram ID</td></tr><tr><td><code>401</code></td><td>API-ключ не передан или недействителен</td><td>Проверьте API-ключ</td></tr><tr><td><code>500</code></td><td>Внутренняя ошибка сервера</td><td>Повторите запрос позже</td></tr></tbody></table>

***

### Общий формат ошибок

Если запрос завершился неуспешно, в ответе всегда будет `"ok": false`:

{% code overflow="wrap" %}

```json
{
  "ok": false,
  "error": "user_id is required",
  "error_code": 400
}
```

{% endcode %}

<table><thead><tr><th width="178.95703125">Поле</th><th>Описание</th></tr></thead><tbody><tr><td><code>ok</code></td><td>Всегда <code>false</code> при ошибке</td></tr><tr><td><code>error</code></td><td>Текстовое описание причины ошибки</td></tr><tr><td><code>error_code</code></td><td>HTTP-код ошибки (совпадает с кодом HTTP-ответа)</td></tr></tbody></table>
