> For the complete documentation index, see [llms.txt](https://docs.graspil.com/en/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/en/api/referral-system.md).

# Referral system

A set of methods for working with the bot's referral program: issuing referral codes, statistics on referred users, reward balance, and payouts.

> Throughout all methods, a "user" means a Telegram user of your bot, and is passed as `user_id` (this is the Telegram ID, not your internal ID).

### How it works in a nutshell

1. You create a referral code for a user (`create-code`) and give them a link like `https://t.me/your_bot?start=CODE`.
2. When a new person follows this link, they are automatically counted as a referral for this user.
3. If you use your own codes (not issued by this API), let the system know who owns which code via `set-code-owners`.
4. To find out how many people a user has referred and how much they're owed, use the `referrals` and `balance` methods.
5. When a user wants to withdraw their reward, call `payout`.
6. If an accrual turns out to be erroneous, it can be reversed via `reverse-accrual`.

{% hint style="info" %}
The referral system is described in more detail in [this section](/en/app/referral-system.md)
{% endhint %}

### 1. Create a referral code

`POST /v1/referral/create-code` (`GET` is also supported)

Creates a new unique referral code for a user and immediately returns a ready-made invitation link. The same user can have any number of codes — each call creates a **new** code, and old ones are not revoked.

#### Parameters

<table><thead><tr><th width="125.87109375">Parameter</th><th width="94.77734375">Type</th><th width="148.21484375">Required</th><th>Description</th></tr></thead><tbody><tr><td><code>user_id</code></td><td>int</td><td>yes</td><td>Telegram ID of the user for whom the code is created (referrer)</td></tr></tbody></table>

#### Example call

{% code overflow="wrap" %}

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

{% endcode %}

#### Response

{% 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">Field</th><th width="108.4375">Type</th><th>Description</th></tr></thead><tbody><tr><td><code>code</code></td><td>string</td><td>The code itself, 8 characters. Digits and letters without easily confused characters (no <code>0</code>, <code>O</code>, <code>1</code>, <code>l</code>, <code>I</code>), so the code is easy to read and enter manually</td></tr><tr><td><code>link</code></td><td>string|null</td><td>Ready-made invitation link in the form <code>https://t.me/&#x3C;bot>?start=&#x3C;code></code>. Will be <code>null</code> if the bot has no username set</td></tr></tbody></table>

#### Possible errors

<table><thead><tr><th width="77.921875">Code</th><th width="303.03125">When it occurs</th><th>How to fix it</th></tr></thead><tbody><tr><td><code>400</code></td><td><code>user_id</code> not provided, or is not a number</td><td>Provide a valid Telegram ID</td></tr><tr><td><code>401</code></td><td>API key not provided or invalid</td><td>Check the <code>Api-Key</code> header / <code>api-key</code> parameter</td></tr><tr><td><code>500</code></td><td>Internal server error</td><td>Retry the request later; if it keeps happening, contact support</td></tr></tbody></table>

***

### 2. Assign an owner to a code (set-code-owners)

2\. Assign an owner to a code (set-code-owners)

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

This method is only needed in one case: if your bot **itself** generates or issues referral codes (not via `create-code`), and our system doesn't know about it. The method tells the system: "this code belongs to this user."

An additional effect is **backfilling**: if there were already visits via this code before you reported the owner, all of them will be retroactively counted for this user as referrals.

You can pass several codes in a single request.

{% hint style="info" %}
We recommend reporting codes in advance whenever possible — this will have a positive effect when building reports in the future
{% endhint %}

#### Parameters (request body — array of objects)

<table><thead><tr><th width="137.59765625">Parameter</th><th width="107.1875">Type</th><th width="153.36328125">Required</th><th>Description</th></tr></thead><tbody><tr><td><code>code</code></td><td>string</td><td>yes</td><td>Referral code</td></tr><tr><td><code>user_id</code></td><td>int</td><td>yes</td><td>Telegram ID of the code owner (referrer)</td></tr></tbody></table>

#### Example call

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

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

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

{% endcode %}

#### Response

{% 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">Field</th><th width="109.48828125">Type</th><th>Description</th></tr></thead><tbody><tr><td><code>code</code></td><td>string</td><td>The code that was processed</td></tr><tr><td><code>owner_id</code></td><td>int</td><td>Telegram ID that was assigned to the code</td></tr><tr><td><code>created</code></td><td>bool</td><td><code>true</code> — the code didn't exist before and was just registered. <code>false</code> — the code already existed and was simply assigned an owner</td></tr><tr><td><code>backfilled</code></td><td>int</td><td>How many past visits via this code retroactively got this owner as their referrer</td></tr></tbody></table>

#### Possible errors

<table><thead><tr><th width="100.359375">Code</th><th>When it occurs</th><th>How to fix it</th></tr></thead><tbody><tr><td><code>400</code></td><td>Request body is not an array, or is an empty array</td><td>Provide a non-empty JSON array of objects</td></tr><tr><td><code>400</code></td><td>One of the objects is missing <code>code</code> or <code>user_id</code> (or <code>user_id</code> equals 0)</td><td>Check that both fields are filled in for each object</td></tr><tr><td><code>401</code></td><td>API key not provided or invalid</td><td>Check the API key</td></tr><tr><td><code>500</code></td><td>Internal server error</td><td>Retry the request later</td></tr></tbody></table>

***

### 3. List of a user's codes

`GET /v1/referral/codes`

Returns all referral codes issued to a specific user via `create-code`, plus the total number of people they've referred (across all of their codes combined).

#### Parameters

<table><thead><tr><th width="161.140625">Parameter</th><th width="113.3203125">Type</th><th width="162.31640625">Required</th><th>Description</th></tr></thead><tbody><tr><td><code>user_id</code></td><td>int</td><td>yes</td><td>Telegram ID of the user</td></tr></tbody></table>

#### Example call

{% code overflow="wrap" %}

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

{% endcode %}

#### Response

{% 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">Field</th><th width="155.05078125">Type</th><th>Description</th></tr></thead><tbody><tr><td><code>total_referrals</code></td><td>int</td><td>Total number of people this user has referred (across all of their codes)</td></tr><tr><td><code>codes</code></td><td>array</td><td>List of codes, newest to oldest</td></tr><tr><td><code>codes[].code</code></td><td>string</td><td>The code</td></tr><tr><td><code>codes[].link</code></td><td>string|null</td><td>Ready-made invitation link</td></tr><tr><td><code>codes[].date_create</code></td><td>string</td><td>Date and time the code was created (<code>YYYY-MM-DD HH:MM:SS</code>, UTC)</td></tr></tbody></table>

> The method only shows codes created via `create-code`. Codes assigned via `set-code-owners` without prior creation will not appear in this list.

#### Possible errors

<table><thead><tr><th width="114.76953125">Code</th><th>When it occurs</th><th>How to fix it</th></tr></thead><tbody><tr><td><code>400</code></td><td><code>user_id</code> not provided, or is not a number</td><td>Provide a valid Telegram ID</td></tr><tr><td><code>401</code></td><td>API key not provided or invalid</td><td>Check the API key</td></tr><tr><td><code>500</code></td><td>Internal server error</td><td>Retry the request later</td></tr></tbody></table>

***

### 4. List of referred users

`GET /v1/referral/referrals`

Returns a list of people the user has referred via their referral codes (across all codes combined), with the date of each visit.

#### Parameters

<table><thead><tr><th width="118.6015625">Parameter</th><th width="83.36328125">Type</th><th width="156.30859375">Required</th><th width="154.06640625">Default</th><th>Description</th></tr></thead><tbody><tr><td><code>user_id</code></td><td>int</td><td>yes</td><td>—</td><td>Telegram ID of the referrer</td></tr><tr><td><code>limit</code></td><td>int</td><td>no</td><td>50</td><td>How many records to return at once (1 to 200; out-of-range values are automatically clamped to the bounds)</td></tr><tr><td><code>offset</code></td><td>int</td><td>no</td><td>0</td><td>How many records to skip from the start of the list — used for pagination</td></tr></tbody></table>

#### Example call

{% code overflow="wrap" %}

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

{% endcode %}

#### Response

{% 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">Field</th><th width="119.75">Type</th><th>Description</th></tr></thead><tbody><tr><td><code>referrals</code></td><td>array</td><td>List of referred users, newest to oldest</td></tr><tr><td><code>referrals[].referral_id</code></td><td>int</td><td>Telegram ID of the referred user</td></tr><tr><td><code>referrals[].date</code></td><td>string</td><td>Date and time the user followed the referral link (UTC)</td></tr></tbody></table>

#### Possible errors

<table><thead><tr><th width="131.79296875">Code</th><th>When it occurs</th><th>How to fix it</th></tr></thead><tbody><tr><td><code>400</code></td><td><code>user_id</code> not provided, or is not a number</td><td>Provide a valid Telegram ID</td></tr><tr><td><code>401</code></td><td>API key not provided or invalid</td><td>Check the API key</td></tr><tr><td><code>500</code></td><td>Internal server error</td><td>Retry the request later</td></tr></tbody></table>

***

### 5. Reward balance

`GET /v1/referral/balance`

Shows how much money (or other reward currency) the user has accumulated for the people they've referred, broken down by status.

#### Parameters

<table><thead><tr><th width="147.6328125">Parameter</th><th width="99.44921875">Type</th><th width="161.9375">Required</th><th>Description</th></tr></thead><tbody><tr><td><code>user_id</code></td><td>int</td><td>yes</td><td>Telegram ID of the referrer</td></tr></tbody></table>

#### Example call

{% code overflow="wrap" %}

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

{% endcode %}

#### Response

{% 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">Field</th><th width="120.7734375">Type</th><th>Description</th></tr></thead><tbody><tr><td><code>balance</code></td><td>array</td><td>One row per currency (<code>unit_id</code>) for which the user has any accruals</td></tr><tr><td><code>balance[].unit_id</code></td><td>int</td><td>Identifier of the reward currency/unit</td></tr><tr><td><code>balance[].available</code></td><td>number</td><td>Available for withdrawal right now</td></tr><tr><td><code>balance[].reserved</code></td><td>number</td><td>Reserved — already included in a created but not yet processed payout</td></tr><tr><td><code>balance[].paid</code></td><td>number</td><td>Already paid out to the user previously</td></tr><tr><td><code>balance[].on_hold</code></td><td>number</td><td>On hold — accrued but not yet confirmed for payout (e.g. pending review)</td></tr></tbody></table>

#### Possible errors

<table><thead><tr><th width="125.20703125">Code</th><th width="297.7265625">When it occurs</th><th>How to fix it</th></tr></thead><tbody><tr><td><code>400</code></td><td><code>user_id</code> not provided, or is not a number</td><td>Provide a valid Telegram ID</td></tr><tr><td><code>401</code></td><td>API key not provided or invalid</td><td>Check the API key</td></tr><tr><td><code>500</code></td><td>Internal server error</td><td>Retry the request later</td></tr></tbody></table>

***

### 6. Request a payout

`POST /v1/referral/payout`

Initiates a payout of the user's entire available (`available`) balance. The method only **creates a payout request** — the actual payout (e.g. sending a webhook notification about the disbursement) is carried out by the system separately, on a schedule.

> Important: the method does not check a minimum withdrawal threshold and does not ask for confirmation — if you call it, a request is created immediately for the full available amount. The decision of when to call this method (e.g. only once the user has accumulated at least 100 ₸) is up to your bot.

#### Parameters

<table><thead><tr><th width="115.65234375">Parameter</th><th width="85.37109375">Type</th><th width="151.8046875">Required</th><th width="149.7734375">Default</th><th>Description</th></tr></thead><tbody><tr><td><code>user_id</code></td><td>int</td><td>yes</td><td>—</td><td>Telegram ID of the referrer</td></tr><tr><td><code>unit_id</code></td><td>int</td><td>no</td><td>not set</td><td>The currency to withdraw funds in. If not provided, the entire available balance is withdrawn across any currency where it exists</td></tr><tr><td><code>method</code></td><td>string</td><td>no</td><td><code>webhook</code></td><td>Payout execution method: <code>webhook</code> — the system will send a payout notification to your webhook itself; <code>manual</code> — the payout will be marked as requiring manual processing by an administrator</td></tr></tbody></table>

#### Example call

`POST /v1/referral/payout`

{% code overflow="wrap" %}

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

{% endcode %}

#### Response

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

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

{% endcode %}

<table><thead><tr><th width="155.09765625">Field</th><th width="113.6015625">Type</th><th>Description</th></tr></thead><tbody><tr><td><code>payout_id</code></td><td>int</td><td>Identifier of the created payout request. Use it to find this payout in <code>GET /v1/referral/payouts</code></td></tr></tbody></table>

#### Possible errors

<table><thead><tr><th width="92.84375">Code</th><th>When it occurs</th><th>How to fix it</th></tr></thead><tbody><tr><td><code>400</code></td><td><code>user_id</code> not provided, or is not a number</td><td>Provide a valid Telegram ID</td></tr><tr><td><code>400</code></td><td>The user has no available balance to withdraw (<code>no available balance</code>)</td><td>Check the balance via <code>GET /v1/referral/balance</code> before calling — there's nothing to withdraw if <code>available</code> is zero</td></tr><tr><td><code>401</code></td><td>API key not provided or invalid</td><td>Check the API key</td></tr><tr><td><code>500</code></td><td>Internal server error</td><td>Retry the request later</td></tr></tbody></table>

***

### 7. Payout history

`GET /v1/referral/payouts`

Returns a list of all of the user's payout requests — both already processed ones and those still in progress.

#### Parameters

| Parameter | Type | Required | Default | Description                                         |
| --------- | ---- | -------- | ------- | --------------------------------------------------- |
| `user_id` | int  | yes      | —       | Telegram ID of the referrer                         |
| `limit`   | int  | no       | 50      | How many records to return at once (1 to 200)       |
| `offset`  | int  | no       | 0       | How many records to skip from the start of the list |

#### Example call

{% code overflow="wrap" %}

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

{% endcode %}

#### Response

{% 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 %}

| Field                    | Type         | Description                                                                                                                        |
| ------------------------ | ------------ | ---------------------------------------------------------------------------------------------------------------------------------- |
| `payouts`                | array        | List of payout requests, newest to oldest                                                                                          |
| `payouts[].payout_id`    | int          | Identifier of the request                                                                                                          |
| `payouts[].amount`       | number       | Payout amount                                                                                                                      |
| `payouts[].unit_id`      | int          | Payout currency/unit                                                                                                               |
| `payouts[].status`       | int          | Numeric status of the request (internal processing stage)                                                                          |
| `payouts[].method`       | string       | Execution method: `webhook` or `manual`                                                                                            |
| `payouts[].initiated_by` | string       | Who initiated the request: `api` (via this method), `admin` (manually in the admin panel), or `auto` (automatically by the system) |
| `payouts[].requested_at` | string       | Date and time the request was created (UTC)                                                                                        |
| `payouts[].processed_at` | string\|null | Date and time the request was actually processed. `null` if not yet processed                                                      |

#### Possible errors

| Code  | When it occurs                             | How to fix it               |
| ----- | ------------------------------------------ | --------------------------- |
| `400` | `user_id` not provided, or is not a number | Provide a valid Telegram ID |
| `401` | API key not provided or invalid            | Check the API key           |
| `500` | Internal server error                      | Retry the request later     |

### 8. Reverse an accrual

`POST /v1/referral/reverse-accrual`

Cancels one specific reward accrual by its identifier (`accrual_id`). Used when an accrual was made by mistake (for example, a referred user's payment was cancelled or refunded).

The behavior depends on the accrual's current state:

* if the accrual hasn't been paid out yet — it's simply marked as cancelled, and the amount disappears from the user's balance;
* if the accrual has already been paid out — a compensating record is created (essentially a "minus" for the same amount), and the accrual itself is marked as reversed.

#### Parameters (request body)

<table><thead><tr><th width="145.31640625">Parameter</th><th width="108.0390625">Type</th><th width="163.51171875">Required</th><th>Description</th></tr></thead><tbody><tr><td><code>accrual_id</code></td><td>int</td><td>yes</td><td>Identifier of the accrual to be reversed</td></tr><tr><td><code>reason</code></td><td>string</td><td>no</td><td>Reason for the reversal — stored for history, doesn't affect anything</td></tr></tbody></table>

#### Example call

{% code overflow="wrap" %}

```json
{ "accrual_id": 99812, "reason": "User's payment was refunded" }
```

{% endcode %}

#### Response

{% code overflow="wrap" %}

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

{% endcode %}

The method returns no additional data — a successful response (`ok: true`) means the accrual has been reversed.

#### Possible errors

<table><thead><tr><th width="110.32421875">Code</th><th>When it occurs</th><th>How to fix it</th></tr></thead><tbody><tr><td><code>400</code></td><td><code>accrual_id</code> not provided</td><td>Provide a valid accrual identifier</td></tr><tr><td><code>404</code></td><td>No accrual found with this <code>accrual_id</code> (or it belongs to another bot)</td><td>Check that <code>accrual_id</code> is correct and belongs to your bot</td></tr><tr><td><code>409</code></td><td>The accrual is already in a state that doesn't allow reversal (e.g. already reversed earlier)</td><td>No need to reverse it again — the accrual is already inactive</td></tr><tr><td><code>401</code></td><td>API key not provided or invalid</td><td>Check the API key</td></tr><tr><td><code>500</code></td><td>Internal server error</td><td>Retry the request later</td></tr></tbody></table>

***

### 9. Mark a user as a referral (mark-referral)

`POST /v1/referral/mark-referral`

Directly marks a user as a referral — without following a referral link. Use this if you already know (from your own data) that a user came from a specific referrer, and just want to inform the system about it.

Specify the referrer **one of two ways**:

* `code` — an existing referral code (issued via `create-code` or reported via `set-code-owners`);
* `referrer_user_id` — the referrer's Telegram ID directly. If they don't have a code yet, one will be created automatically.

You cannot pass both parameters at the same time.

The method is idempotent: if the user already has a linked referrer (including from an earlier link visit), a repeat call changes nothing — the response returns the existing referrer.

#### Parameters (request body)

<table><thead><tr><th width="180.45703125">Parameter</th><th width="85.734375">Type</th><th width="218.0625">Required</th><th>Description</th></tr></thead><tbody><tr><td><code>referral_user_id</code></td><td>int</td><td>yes</td><td>Telegram ID of the user to be marked as a referral</td></tr><tr><td><code>code</code></td><td>string</td><td>one of two (<code>code</code> or <code>referrer_user_id</code>)</td><td>An existing referral code of the referrer</td></tr><tr><td><code>referrer_user_id</code></td><td>int</td><td>one of two (<code>code</code> or <code>referrer_user_id</code>)</td><td>Telegram ID of the referrer</td></tr></tbody></table>

#### Example call

{% code overflow="wrap" %}

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

{% endcode %}

#### Response

{% 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">Field</th><th width="123.703125">Type</th><th>Description</th></tr></thead><tbody><tr><td><code>referral_id</code></td><td>int</td><td>Telegram ID of the marked referral (same as <code>referral_user_id</code>)</td></tr><tr><td><code>referrer_id</code></td><td>int|null</td><td>Telegram ID of the referrer. <code>null</code> is only possible when linking by <code>code</code>, if that code doesn't yet have a known owner</td></tr><tr><td><code>code</code></td><td>string|null</td><td>The code through which the link was made. <code>null</code> if the link was made via <code>referrer_user_id</code> and the referrer already had their own code (it was used, but its value isn't returned)</td></tr><tr><td><code>already_attributed</code></td><td>bool</td><td><code>true</code> — the user already had a referrer (this request changed nothing, returned what already existed). <code>false</code> — the link was created by this call</td></tr></tbody></table>

#### Possible errors

<table><thead><tr><th width="78.59765625">Code</th><th>When it occurs</th><th>How to fix it</th></tr></thead><tbody><tr><td><code>400</code></td><td><code>referral_user_id</code> not provided, or both <code>code</code> and <code>referrer_user_id</code> were provided at the same time, or neither was provided</td><td>Provide <code>referral_user_id</code> and exactly one of <code>code</code>/<code>referrer_user_id</code></td></tr><tr><td><code>400</code></td><td>A user is attempting to become a referral of themselves</td><td>Check that <code>referral_user_id</code> and the referrer are different users</td></tr><tr><td><code>404</code></td><td>The specified <code>code</code> was not found</td><td>Check the code, or use <code>referrer_user_id</code> instead</td></tr><tr><td><code>401</code></td><td>API key not provided or invalid</td><td>Check the API key</td></tr><tr><td><code>500</code></td><td>Internal server error</td><td>Retry the request later</td></tr></tbody></table>

***

### 10. List of accruals

`GET /v1/referral/accruals`

Returns the income side of the user's balance history — a list of individual reward accruals (one row per referred user and event). Withdrawals (payouts) are returned by `GET /v1/referral/payouts` — together, these two methods provide the full feed of balance movements.

Reversed and cancelled accruals (see `reverse-accrual`) do not appear in this list — this is a ledger of the "live" balance, not a complete audit trail.

#### Parameters

<table><thead><tr><th width="122.765625">Parameter</th><th width="87.15234375">Type</th><th width="148.65234375">Required</th><th width="148.828125">Default</th><th>Description</th></tr></thead><tbody><tr><td><code>user_id</code></td><td>int</td><td>yes</td><td>—</td><td>Telegram ID of the referrer</td></tr><tr><td><code>limit</code></td><td>int</td><td>no</td><td>50</td><td>How many records to return at once (1 to 200)</td></tr><tr><td><code>offset</code></td><td>int</td><td>no</td><td>0</td><td>How many records to skip from the start of the list</td></tr></tbody></table>

#### Example call

{% code overflow="wrap" %}

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

{% endcode %}

#### Response

{% 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">Field</th><th width="133.62109375">Type</th><th>Description</th></tr></thead><tbody><tr><td><code>accruals</code></td><td>array</td><td>List of accruals, newest to oldest</td></tr><tr><td><code>accruals[].id</code></td><td>int</td><td>Identifier of the accrual (used as <code>accrual_id</code> in <code>reverse-accrual</code>)</td></tr><tr><td><code>accruals[].date</code></td><td>string</td><td>Date and time the accrual was created (UTC)</td></tr><tr><td><code>accruals[].referral_id</code></td><td>int</td><td>Telegram ID of the referred user whose action triggered the reward</td></tr><tr><td><code>accruals[].amount</code></td><td>number</td><td>Accrual amount (can be negative — a compensating record after a reversal)</td></tr><tr><td><code>accruals[].unit_id</code></td><td>int|null</td><td>Currency/unit of the reward</td></tr><tr><td><code>accruals[].status</code></td><td>int</td><td><code>0</code> — on hold, <code>1</code> — confirmed (available for withdrawal), <code>2</code> — paid out</td></tr><tr><td><code>accruals[].available_at</code></td><td>string|null</td><td>When the accrual will become available for withdrawal (holding period). <code>null</code> if no holding period applies or it has already passed</td></tr></tbody></table>

#### Possible errors

<table><thead><tr><th width="106.95703125">Code</th><th width="302.6328125">When it occurs</th><th>How to fix it</th></tr></thead><tbody><tr><td><code>400</code></td><td><code>user_id</code> not provided, or is not a number</td><td>Provide a valid Telegram ID</td></tr><tr><td><code>401</code></td><td>API key not provided or invalid</td><td>Check the API key</td></tr><tr><td><code>500</code></td><td>Internal server error</td><td>Retry the request later</td></tr></tbody></table>

***

### 11. Current reward rate

`GET /v1/referral/rates`

Shows the terms under which a user receives a reward for referrals — for each active program rule separately (if the bot has several rules, e.g. for payments and for subscriptions). Useful for showing the user "you receive 10% of every payment" in your interface.

If the user has already had at least one accrual under a rule, their locked-in rate is returned (`locked: true`), even if the program's general rate has changed since then. If there have been no accruals yet, the rule's current rate is returned (`locked: false`); it will be locked in for the user automatically upon the first applicable accrual.

#### Parameters

<table><thead><tr><th width="130.578125">Parameter</th><th width="88.32421875">Type</th><th width="165.09375">Required</th><th>Description</th></tr></thead><tbody><tr><td><code>user_id</code></td><td>int</td><td>yes</td><td>Telegram ID of the referrer</td></tr></tbody></table>

#### Example call

{% code overflow="wrap" %}

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

{% endcode %}

#### Response

{% code overflow="wrap" %}

```json
{
  "ok": true,
  "data": {
    "rates": [
      {
        "rule_id": 7,
        "rule_name": "Percentage of payments",
        "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">Field</th><th width="132.515625">Type</th><th>Description</th></tr></thead><tbody><tr><td><code>rates</code></td><td>array</td><td>One row per active program rule for the bot</td></tr><tr><td><code>rates[].rule_id</code></td><td>int</td><td>Identifier of the rule</td></tr><tr><td><code>rates[].rule_name</code></td><td>string|null</td><td>Name of the rule (as set in the admin panel)</td></tr><tr><td><code>rates[].trigger_event_id</code></td><td>int</td><td>Type of event for which the reward is granted under this rule</td></tr><tr><td><code>rates[].reward_type</code></td><td>string</td><td><code>fixed</code> — a fixed amount, <code>percent</code> — a percentage of the event amount, <code>external</code> — the reward is issued on your side</td></tr><tr><td><code>rates[].reward_amount</code></td><td>number|null</td><td>Reward amount for <code>fixed</code></td></tr><tr><td><code>rates[].reward_percent</code></td><td>number|null</td><td>Reward percentage for <code>percent</code></td></tr><tr><td><code>rates[].unit_id</code></td><td>int|null</td><td>Currency/unit of the reward</td></tr><tr><td><code>rates[].locked</code></td><td>bool</td><td><code>true</code> — the rate is locked in specifically for this user (won't change if the program's overall rate is edited); <code>false</code> — the program's current general rate applies</td></tr><tr><td><code>rates[].source</code></td><td>string|null</td><td><code>auto</code> — locked in automatically on the first accrual; <code>manual</code> — set individually by an administrator; <code>null</code> if <code>locked</code> is <code>false</code></td></tr></tbody></table>

#### Possible errors

<table><thead><tr><th width="111.2890625">Code</th><th>When it occurs</th><th>How to fix it</th></tr></thead><tbody><tr><td><code>400</code></td><td><code>user_id</code> not provided, or is not a number</td><td>Provide a valid Telegram ID</td></tr><tr><td><code>401</code></td><td>API key not provided or invalid</td><td>Check the API key</td></tr><tr><td><code>500</code></td><td>Internal server error</td><td>Retry the request later</td></tr></tbody></table>

***

### Common error format

If a request fails, the response will always contain `"ok": false`:

{% code overflow="wrap" %}

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

{% endcode %}

<table><thead><tr><th width="178.95703125">Field</th><th>Description</th></tr></thead><tbody><tr><td><code>ok</code></td><td>Always <code>false</code> on error</td></tr><tr><td><code>error</code></td><td>Text description of the error reason</td></tr><tr><td><code>error_code</code></td><td>HTTP error code (matches the HTTP response status code)</td></tr></tbody></table>
