> ## Documentation Index
> Fetch the complete documentation index at: https://docs.rek.la/llms.txt
> Use this file to discover all available pages before exploring further.

# External API

> Интеграция с Rekla: создание публикаций в Telegram-каналах через API

Rekla External API позволяет внешним партнёрам создавать публикации в Telegram-каналах пользователей Rekla, проверять доступ и отслеживать статусы.

**Base URL:** `https://api.rek.la`

***

## Подключение

<Steps>
  <Step title="Запросите доступ">
    Напишите в **[@rekla\_support](https://telegram.me/rekla_support)**:

    > «Хочу подключиться как партнёр к Rekla External API»

    Вам выдадут:

    * **Partner ID** — для заголовка `X-Partner-Id`
    * **Webhook Secret** — для проверки подписи вебхуков
    * **Start-команда** — для создания API-ключей пользователями
  </Step>

  <Step title="Получите API-ключ пользователя">
    Пользователь вводит команду в боте **[@rekla](https://telegram.me/rekla)**:

    ```
    /start createapikey-<partner_name>
    ```

    Бот создаёт ключ и показывает его пользователю. Одновременно Rekla отправит вебхук `api_key.created` на ваш сервер с данными ключа.
  </Step>

  <Step title="Делайте запросы">
    ```bash theme={null}
    curl -H "X-API-Key: rekla_api:xxx" \
         -H "X-Partner-Id: your_partner_id" \
         https://api.rek.la/external-api/me
    ```
  </Step>
</Steps>

***

## Аутентификация

| Заголовок      | Описание                    |
| -------------- | --------------------------- |
| `X-API-Key`    | API-ключ пользователя Rekla |
| `X-Partner-Id` | Ваш идентификатор партнёра  |

<Warning>
  API-ключ показывается пользователю один раз. Повторно получить его невозможно.
</Warning>

***

## Эндпоинты

### GET /external-api/me

Получить информацию о владельце API-ключа.

```bash theme={null}
curl -H "X-API-Key: rekla_api:xxx" \
     -H "X-Partner-Id: your_partner_id" \
     https://api.rek.la/external-api/me
```

**Ответ `200`:**

```json theme={null}
{
  "id": 753967249,
  "name": "Максим",
  "username": "m5286606"
}
```

***

### GET /external-api/channels/{channel_id}/can-post

Проверить, может ли пользователь публиковать в указанный канал.

Проверяется два условия:

1. У пользователя есть права автопостинга в канале
2. Бот Rekla является администратором канала с правами на публикацию

```bash theme={null}
curl -H "X-API-Key: rekla_api:xxx" \
     -H "X-Partner-Id: your_partner_id" \
     https://api.rek.la/external-api/channels/-1001568992973/can-post
```

**Ответ `200`:**

```json theme={null}
{
  "autoposting_access": true
}
```

**Ответ `403`:**

```json theme={null}
{
  "detail": "Access denied. You do not have permission in Rekla to post into this channel."
}
```

<Info>
  `channel_id` — это числовой Telegram ID канала (обычно начинается с `-100`).
</Info>

***

### POST /external-api/publications

Создать одну или несколько публикаций. Принимает массив объектов.

```bash theme={null}
curl -X POST \
     -H "Content-Type: application/json" \
     -H "X-API-Key: rekla_api:xxx" \
     -H "X-Partner-Id: your_partner_id" \
     -d '[{
       "external_id": "pub-001",
       "channel_id": -1001568992973,
       "text": "Текст публикации",
       "text_len": 16,
       "media_type": "text",
       "price": 1500,
       "publish_date": "2030-03-01T12:00:00Z",
       "payment_status": "not_paid",
       "allow_comments": true
     }]' \
     https://api.rek.la/external-api/publications
```

**Ответ `201`:**

```json theme={null}
{
  "publications": [
    {
      "id": 11607,
      "external_id": "pub-001"
    }
  ]
}
```

<AccordionGroup>
  <Accordion title="Поля PublicationCreateRequest">
    | Поле                      | Тип       | Обязательно | Описание                                                                                                                |
    | ------------------------- | --------- | :---------: | ----------------------------------------------------------------------------------------------------------------------- |
    | `external_id`             | `string`  |      ✅      | Ваш внутренний ID публикации                                                                                            |
    | `channel_id`              | `integer` |      ✅      | Telegram ID канала                                                                                                      |
    | `text`                    | `string`  |      —      | Текст публикации (HTML-разметка Telegram)                                                                               |
    | `text_len`                | `integer` |      —      | Длина текста                                                                                                            |
    | `media_type`              | `string`  |      —      | Тип: `text`, `photo`, `video`, `animation`, `document`, `audio`, `voice`, `video_note`, `album`, `poll`, `rich_message` |
    | `rich_message`            | `object`  |      —      | Статья: обязательна при `media_type: rich_message`                                                                      |
    | `file_id`                 | `string`  |      —      | Устаревшее внешнее поле; `file_id` другого бота использовать нельзя                                                     |
    | `file_url`                | `string`  |      —      | HTTP(S) URL обычного медиа; Rekla сразу получит собственный `file_id`                                                   |
    | `album`                   | `object`  |      —      | Конфигурация альбома (обязательно при `media_type: album`)                                                              |
    | `poll`                    | `object`  |      —      | Конфигурация опроса (при `media_type: poll`)                                                                            |
    | `inline_buttons`          | `object`  |      —      | Инлайн-кнопки                                                                                                           |
    | `price`                   | `number`  |      ✅      | Цена публикации                                                                                                         |
    | `publish_date`            | `string`  |      ✅      | Дата публикации (ISO 8601)                                                                                              |
    | `payment_status`          | `string`  |      —      | `not_paid` (по умолчанию), `paid`, `partial_paid`, `overpaid`, `refund`, `refund_partial`                               |
    | `auto_delete`             | `integer` |      —      | Автоудаление через N минут                                                                                              |
    | `with_sound_notification` | `boolean` |      —      | Звуковое уведомление (`false` по умолчанию)                                                                             |
    | `wp_preview`              | `boolean` |      —      | Предпросмотр ссылок                                                                                                     |
    | `wp_preview_config`       | `object`  |      —      | Настройки предпросмотра: `is_disabled`, `url`, `prefer_small_media`, `show_above_text`                                  |
    | `allow_comments`          | `boolean` |      —      | Разрешить комментарии (`true` по умолчанию)                                                                             |
    | `has_spoiler`             | `boolean` |      —      | Спойлер на медиа                                                                                                        |
    | `protect_content`         | `boolean` |      —      | Защита от копирования                                                                                                   |
    | `comment`                 | `string`  |      —      | Комментарий к публикации                                                                                                |
  </Accordion>

  <Accordion title="Правила валидации">
    **Альбомы (`media_type: album`):** поле `album` обязательно и не может быть пустым. Только последний элемент альбома может содержать `caption`. Поле `text` должно совпадать с `caption` последнего элемента.

    **Обычные медиа:** для одиночного медиа и каждого элемента альбома обязателен публично доступный `file_url`. `file_id`, полученный другим Telegram-ботом, бот Rekla использовать не может.

    **Статья (`media_type: rich_message`):** передайте точные `blocks` и `is_rtl` из `message.rich_message`, полученного ботом партнёра. Для каждого встроенного фото, видео, аудио, animation или voice note добавьте в `media_urls` соответствие `file_unique_id → временный HTTP(S) URL`.

    Rekla загрузит все медиа своим ботом прямо во время создания публикации. К моменту сохранения Статья уже содержит наши Telegram `file_id`; переданные URL не нужны крону и паблишеру.

    URL вида `https://api.telegram.org/file/bot<TOKEN>/...` запрещён, потому что раскрывает токен партнёрского бота. Сначала скачайте файл своим ботом и разместите его по безопасному временному URL.

    **Дата публикации:** принимается в формате ISO 8601. Таймзона автоматически удаляется (хранится как naive datetime).
  </Accordion>

  <Accordion title="Пример с фото">
    ```json theme={null}
    [{
      "external_id": "pub-photo-001",
      "channel_id": -1001568992973,
      "text": "Подпись к фото",
      "text_len": 14,
      "media_type": "photo",
      "file_url": "https://example.com/image.jpg",
      "album": null,
      "poll": null,
      "inline_buttons": {
        "inline_keyboard": [[
          {"text": "Подробнее", "url": "https://example.com"}
        ]]
      },
      "price": 2000,
      "publish_date": "2030-03-01T14:00:00Z",
      "payment_status": "paid",
      "auto_delete": 1440,
      "with_sound_notification": false,
      "wp_preview": false,
      "allow_comments": false,
      "has_spoiler": false,
      "protect_content": true,
      "wp_preview_config": null,
      "comment": "Рекламная кампания #1"
    }]
    ```
  </Accordion>

  <Accordion title="Пример Статьи с фото и кнопкой">
    ```json theme={null}
    [{
      "external_id": "article-001",
      "channel_id": -1001568992973,
      "media_type": "rich_message",
      "rich_message": {
        "blocks": [
          {
            "type": "heading",
            "text": "Новая функция",
            "size": 2
          },
          {
            "type": "paragraph",
            "text": "Теперь в Rekla можно публиковать Статьи."
          },
          {
            "type": "photo",
            "photo": [{
              "file_id": "partner-bot-file-id",
              "file_unique_id": "AQAD-example-photo",
              "width": 1280,
              "height": 720
            }],
            "caption": {
              "text": "Подпись к фотографии"
            }
          }
        ],
        "is_rtl": false,
        "media_urls": {
          "AQAD-example-photo": "https://partner.example/media/article-photo.jpg"
        }
      },
      "inline_buttons": {
        "inline_keyboard": [[
          {"text": "Подробнее", "url": "https://example.com"}
        ]]
      },
      "price": 2000,
      "publish_date": "2030-03-01T14:00:00Z"
    }]
    ```

    `inline_buttons` остаётся верхнеуровневым полем. Если партнёр получил публикацию от своего бота, сюда передаётся `message.reply_markup`, а в `rich_message` — `message.rich_message`.
  </Accordion>
</AccordionGroup>

**Коды ответов:**

| Код   | Описание                                                                          |
| ----- | --------------------------------------------------------------------------------- |
| `201` | Публикации созданы                                                                |
| `403` | Нет доступа к одному или нескольким каналам                                       |
| `422` | Семантическая ошибка Статьи: отсутствует URL медиа или получен неверный тип файла |
| `400` | Структурная ошибка JSON/Pydantic или другая ошибка запроса                        |

<Info>
  Создание атомарно: если одна публикация из массива не пройдёт валидацию, ни одна не будет создана.
</Info>

***

### POST /external-api/publications/cancel

Отменить публикацию.

```bash theme={null}
curl -X POST \
     -H "Content-Type: application/json" \
     -H "X-API-Key: rekla_api:xxx" \
     -H "X-Partner-Id: your_partner_id" \
     -d '{"publication_id": 11607, "channel_id": -1001568992973}' \
     https://api.rek.la/external-api/publications/cancel
```

**Ответ `204`:** пустое тело (успешная отмена).

<Warning>
  Отменить можно только публикацию, созданную через вашего партнёра. Попытка отменить чужую публикацию вернёт `404`.
</Warning>

***

### POST /external-api/publications/status

Проверить статус публикации.

```bash theme={null}
curl -X POST \
     -H "Content-Type: application/json" \
     -H "X-API-Key: rekla_api:xxx" \
     -H "X-Partner-Id: your_partner_id" \
     -d '{"publication_id": 11607, "channel_id": -1001568992973}' \
     https://api.rek.la/external-api/publications/status
```

**Ответ `200`:**

```json theme={null}
{
  "publication_id": 11607,
  "publication_link": "https://t.me/c/1568992973/42",
  "publish_date": "2030-03-01T12:00:00",
  "auto_delete": null,
  "publish_status": "published",
  "payment_status": "not_paid",
  "delete_date": null
}
```

<AccordionGroup>
  <Accordion title="Возможные значения publish_status">
    | Статус      | Описание                             |
    | ----------- | ------------------------------------ |
    | `created`   | Публикация создана, ожидает отправки |
    | `published` | Опубликована в канале                |
    | `canceled`  | Отменена                             |
    | `deleted`   | Удалена из канала                    |
    | `error`     | Ошибка при публикации                |
  </Accordion>
</AccordionGroup>

***

## Вебхуки

При определённых событиях Rekla отправляет POST-запрос на ваш `webhook_url`.

### Событие `api_key.created`

Отправляется когда пользователь создаёт API-ключ через команду `/start createapikey-<partner>`.

**Тело запроса:**

```json theme={null}
{
  "user_id": 753967249,
  "raw_api_key": "rekla_api:fb27e09cd41f8a2e3dd93a..."
}
```

**Заголовки:**

| Заголовок             | Описание                        |
| --------------------- | ------------------------------- |
| `X-Webhook-Event`     | Тип события (`api_key.created`) |
| `X-Webhook-Id`        | Уникальный UUID события         |
| `X-Webhook-Timestamp` | UNIX timestamp отправки         |
| `X-Webhook-Signature` | Подпись HMAC SHA256             |

### Проверка подписи

<CodeGroup>
  ```python Python theme={null}
  import hmac, hashlib

  def verify_webhook(body: bytes, timestamp: str, signature: str, secret: str) -> bool:
      expected = hmac.new(
          key=secret.encode(),
          msg=f"{timestamp}.{body.decode()}".encode(),
          digestmod=hashlib.sha256
      ).hexdigest()
      return hmac.compare_digest(signature, expected)
  ```

  ```javascript Node.js theme={null}
  const crypto = require('crypto');

  function verifyWebhook(body, timestamp, signature, secret) {
    const expected = crypto
      .createHmac('sha256', secret)
      .update(`${timestamp}.${body}`)
      .digest('hex');
    return crypto.timingSafeEqual(
      Buffer.from(signature),
      Buffer.from(expected)
    );
  }
  ```
</CodeGroup>

***

## Контакты

Поддержка интеграций: **[@rekla\_support](https://telegram.me/rekla_support)**

Версия API: `1.0.0` | Формат: `application/json`

<Info>
  Используя API, вы принимаете [условия оферты](/api/terms).
</Info>
