Publisher API

Publisher API — это REST-интерфейс MotivBuy, через который ваш бот в МАКС запрашивает у платформы набор обязательных подписок для конкретного пользователя и сообщает о выполнении задания. Все запросы — JSON, аутентификация через Bearer-токен, выданный для каждого подключённого бота.

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

Каждый запрос должен содержать заголовок Authorization: Bearer <api_key>. API-ключ привязан к одному боту: токен, использованный для чужого бота, вернёт ошибку 403 forbidden_error.

Получить ключ можно в личном кабинете MotivBuy: откройте карточку бота → раздел Интеграция → кнопка Скопировать API-ключ. Там же доступна ротация ключа — после неё старое значение мгновенно перестаёт работать.

Никогда не публикуйте API-ключ

API-ключ даёт право отправлять сообщения от имени вашего бота и тратить ваш баланс на показы. Храните его в переменных окружения, не коммитьте в git и не передавайте через мессенджеры. При подозрении на утечку — немедленно перевыпустите ключ в кабинете.

Базовый URL

Все эндпоинты доступны по адресу https://api.motivbuy.ru. Прод-хост может быть заменён вашим выделенным доменом — в этом случае значение придёт вместе с приглашением в программу.

Поток интеграции

Типовой сценарий встраивания MotivBuy в пользовательский путь занимает два сетевых вызова:

  1. Перед целевым действием (скачивание, поиск, выдача премиум-контента — не на каждый /start) вызовите POST /publisher/request с идентификаторами пользователя и чата.
  2. Если в ответе all_subscribed: true — обязательные подписки уже выполнены, выполняйте целевое действие.
  3. Иначе MotivBuy сам отправит пользователю OP-сообщение через токен вашего бота — со списком спонсоров и кнопкой проверки op:check.
  4. Когда пользователь нажимает op:check, ваш бот вызывает POST /publisher/verify. Если all_subscribed: true — задание выполнено, переходите к основному контенту.

POST /publisher/request

Запрашивает у MotivBuy набор обязательных подписок для конкретного пользователя. Вызывается на старте диалога и перед показом любого чувствительного контента.

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

ПолеТипОбязательноеОписание
max_user_idintegerдаID пользователя МАКС (берётся из любого события: BotStarted, MessageCreated.sender или MessageCallback.user).
chat_idintegerдаID диалога с этим пользователем, куда отправлять OP-сообщение.
country_codestring (2)нетISO-код страны для демографического таргетинга, например "RU".
language_codestring (≤8)нетЯзык интерфейса пользователя, например "ru".
ageinteger (0..120)нетВозраст пользователя, если он известен.
gender"MALE" | "FEMALE"нетПол пользователя.
is_premiumbooleanнетПризнак премиум-пользователя.
op_textstring (≤1024)нетТекст OP-сообщения, переопределяет шаблон бота для этого вызова.
check_buttonstring (≤64)нетТекст кнопки проверки подписок для этого вызова.

Примеры запроса

curlbash
curl -X POST https://api.motivbuy.ru/api/v1/publisher/request \
  -H "Authorization: Bearer sk_live_xxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "max_user_id": 123456789,
    "chat_id": 987654321,
    "country_code": "RU",
    "language_code": "ru",
    "age": 27,
    "gender": "MALE",
    "is_premium": false
  }'
aiohttppython
import aiohttp

API_KEY = "sk_live_xxxxxxxxxxxxxxxxxxxxxxxx"
BASE_URL = "https://api.motivbuy.ru"


async def request_subscriptions(max_user_id: int, chat_id: int) -> dict:
    headers = {"Authorization": f"Bearer {API_KEY}"}
    payload = {
        "max_user_id": max_user_id,
        "chat_id": chat_id,
        "country_code": "RU",
        "language_code": "ru",
        "is_premium": False,
    }
    async with aiohttp.ClientSession(headers=headers) as session:
        async with session.post(
            f"{BASE_URL}/api/v1/publisher/request",
            json=payload,
        ) as response:
            response.raise_for_status()
            return await response.json()
Node.js (fetch)javascript
const API_KEY = "sk_live_xxxxxxxxxxxxxxxxxxxxxxxx";
const BASE_URL = "https://api.motivbuy.ru";

async function requestSubscriptions(maxUserId, chatId) {
  const response = await fetch(`${BASE_URL}/api/v1/publisher/request`, {
    method: "POST",
    headers: {
      Authorization: `Bearer ${API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      max_user_id: maxUserId,
      chat_id: chatId,
      country_code: "RU",
      language_code: "ru",
      is_premium: false,
    }),
  });

  if (!response.ok) {
    throw new Error(`MotivBuy /request failed: ${response.status}`);
  }
  return response.json();
}

Ответ 200 OK

responsejson
{
  "all_subscribed": false,
  "channels_left": 3,
  "sent_message_id": "mid.12345"
}
  • all_subscribed: true — пользователь уже состоит во всех обязательных каналах. OP-сообщение не отправлено, старое (если было) удалено автоматически. Продолжайте основной сценарий /start.
  • all_subscribed: false — MotivBuy отправил OP-сообщение в указанный chat_id через токен вашего бота. В sent_message_id возвращается ID отправленного сообщения, в channels_left — сколько ещё подписок осталось выполнить.

POST /publisher/verify

Проверяет, выполнил ли пользователь обязательные подписки. Вызывается, когда пользователь нажимает кнопку op:check под OP-сообщением.

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

ПолеТипОбязательноеОписание
max_user_idintegerдаID пользователя МАКС.
chat_idintegerдаID диалога с пользователем.
op_textstring (≤1024)нетПереопределение текста OP-сообщения, если оно будет переотправлено.
check_buttonstring (≤64)нетПереопределение текста кнопки проверки.

Примеры запроса

curlbash
curl -X POST https://api.motivbuy.ru/api/v1/publisher/verify \
  -H "Authorization: Bearer sk_live_xxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "max_user_id": 123456789,
    "chat_id": 987654321
  }'
aiohttppython
import aiohttp

API_KEY = "sk_live_xxxxxxxxxxxxxxxxxxxxxxxx"
BASE_URL = "https://api.motivbuy.ru"


async def verify_subscriptions(max_user_id: int, chat_id: int) -> dict:
    headers = {"Authorization": f"Bearer {API_KEY}"}
    payload = {"max_user_id": max_user_id, "chat_id": chat_id}
    async with aiohttp.ClientSession(headers=headers) as session:
        async with session.post(
            f"{BASE_URL}/api/v1/publisher/verify",
            json=payload,
        ) as response:
            response.raise_for_status()
            return await response.json()
Node.js (fetch)javascript
const API_KEY = "sk_live_xxxxxxxxxxxxxxxxxxxxxxxx";
const BASE_URL = "https://api.motivbuy.ru";

async function verifySubscriptions(maxUserId, chatId) {
  const response = await fetch(`${BASE_URL}/api/v1/publisher/verify`, {
    method: "POST",
    headers: {
      Authorization: `Bearer ${API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      max_user_id: maxUserId,
      chat_id: chatId,
    }),
  });

  if (!response.ok) {
    throw new Error(`MotivBuy /verify failed: ${response.status}`);
  }
  return response.json();
}

Ответ 200 OK

responsejson
{
  "all_subscribed": true,
  "channels_left": 0,
  "sent_message_id": null
}

Структура ответа идентична /publisher/request. Если all_subscribed: true — задание выполнено, открывайте пользователю основной контент. Иначе — MotivBuy обновит OP-сообщение со списком оставшихся подписок, пользователю нужно нажать кнопку проверки ещё раз.

Ошибки

Все ошибки возвращаются в формате { "code": "...", "message": "..." } с соответствующим HTTP-статусом.

СтатусcodeПричина
401auth_errorОтсутствует или некорректен заголовок Authorization.
403forbidden_errorБот не одобрен модерацией или ключ принадлежит другому боту.
404not_foundРесурс не найден (например, пользователь или чат не существует).
422validation_errorНевалидное тело запроса: не прошли проверки типов, длины или диапазонов.
429rate_limit_errorПревышен лимит запросов: глобальный по ключу или по паре (ключ, пользователь).

Лимиты

Запросы ограничиваются на двух уровнях: глобально по API-ключу и отдельно по паре (api_key, max_user_id) — не чаще одного запроса в секунду на конкретного пользователя. При превышении возвращается 429 rate_limit_error; повторяйте запрос с экспоненциальной задержкой.

Best practices

  • Идемпотентность. Повторный вызов /request с тем же max_user_id в течение срока сброса ссылки возвращает тот же набор спонсоров — можно безопасно повторять запросы при перезапуске диалога.
  • Валидация чата. Всегда передавайте chat_id ровно тот, что пришёл в событии BotStarted: платформа проверяет, что чат принадлежит боту, выпустившему ключ.
  • Кеш на 30 секунд. Кешируйте последний ответ /request для пары (api_key, max_user_id) на 30 секунд — это спасёт от двойных кликов по стартовой кнопке и лишних 429.
  • Не дёргайте /verify до клика. Эндпоинт рассчитан на ответ на callback op:check. Пуш-опросы по таймеру быстро упрутся в rate-limit.
  • Логируйте sent_message_id. Это пригодится при разборе инцидентов и для серверной аналитики доставки OP-сообщений.