EVIR
Документация API

API EVIR

Получайте карточки через /next, отправляйте их пользователю и подтверждайте успешную отправку.

Python / Node.js /next → /served Секреты только на сервере

Быстрый старт

Нужны два значения и один обработчик команды. Начните с готового примера ниже.

EVIR_CONNECTION_IDCONNECTION_ID
ID находится в карточке подключённого бота.

EVIR_API_KEY — имя переменной окружения. Сам ключ храните только на сервере; ссылка на эту страницу его не содержит.

Готовый примерСкопируйте файл и подключите функции к обработчикам вашего бота.
import os
import time
import uuid
import requests
from urllib.parse import quote

EVIR_URL = "https://evir.bot/api/v1"
EVIR_KEY = os.environ["EVIR_API_KEY"]
EVIR_CONNECTION_ID = os.getenv("EVIR_CONNECTION_ID", "CONNECTION_ID")
session = requests.Session()

class EvirError(RuntimeError):
    def __init__(self, message, status=None, code=None):
        super().__init__(message)
        self.status = status
        self.code = code

def evir_post(path, payload, operation_id=None):
    operation_id = operation_id or str(uuid.uuid4())

    for attempt in range(3):
        try:
            response = session.post(
                f"{EVIR_URL}/integrations/{EVIR_CONNECTION_ID}{path}",
                headers={
                    "Authorization": f"Bearer {EVIR_KEY}",
                    "Content-Type": "application/json",
                    "Idempotency-Key": operation_id,
                },
                json=payload,
                timeout=(3, 10),
            )
        except requests.RequestException:
            if attempt == 2:
                raise
            time.sleep(0.5 * (2 ** attempt))
            continue

        if response.status_code == 429 and attempt < 2:
            try:
                delay = max(1.0, float(response.headers.get("Retry-After", "1")))
            except ValueError:
                delay = 1.0
            time.sleep(delay)
            continue

        if response.status_code >= 500 and attempt < 2:
            time.sleep(0.5 * (2 ** attempt))
            continue

        try:
            body = response.json()
        except ValueError:
            body = {}

        if not response.ok:
            error = body.get("error", {})
            raise EvirError(
                error.get("message", f"EVIR: HTTP {response.status_code}"),
                status=response.status_code,
                code=error.get("code"),
            )
        return body["data"]

# Вызывайте при /offers или в другом добровольном месте показа.
def get_sponsors(telegram_user_id, is_premium=False):
    data = evir_post("/next", {
        "recipientId": str(telegram_user_id),
        "limit": 3,
        "isPremium": bool(is_premium),
    })
    return data["assignments"]  # [] — сейчас подходящих предложений нет

# Не включайте parse_mode: title и description показываются обычным текстом.
def to_telegram_card(assignment):
    keyboard = [[{
        "text": assignment["ctaLabel"],
        "url": assignment["actionUrl"],  # используйте URL без изменений
    }]]

    if assignment.get("requiresQualification"):
        keyboard.append([{
            "text": "Проверить",
            "callback_data": f'evir_qualify:{assignment["deliveryId"]}',
        }])

    return {
        "text": f'{assignment["title"]}\n\n{assignment["description"]}',
        "reply_markup": {"inline_keyboard": keyboard},
    }

# /served подтверждает отправку карточки: вызывайте только после успешного ответа Telegram.
def confirm_card_served(delivery_id):
    return evir_post(
        f"/deliveries/{quote(str(delivery_id), safe='')}/served",
        {},
    )

# Обработчик callback-кнопки проверки: подтвердить действие пользователя в ОП канала.
def qualify_delivery(telegram_user_id, delivery_id):
    return evir_post(
        f"/deliveries/{quote(str(delivery_id), safe='')}/qualify",
        {"recipientId": str(telegram_user_id)},
    )

# В production храните локальное состояние выдачи: allocated → sending → sent → acked.
# Если результат Telegram неопределён, не отправляйте эту карточку повторно.
# После подтверждённой отправки повторяйте только EVIR /served, пока не получите ack.
# Если sendTelegram завершился ошибкой, /served не вызывается.
def send_sponsor_card(send_telegram, telegram_user_id, assignment):
    send_telegram(telegram_user_id, to_telegram_card(assignment))
    needs_served_ack = (
        assignment.get("productType") in ("impression", "transition")
        or (
            assignment.get("productType") == "op"
            and assignment.get("targetType") == "bot"
        )
    )
    if needs_served_ack:
        return confirm_card_served(assignment["deliveryId"])
    return None

# Только для legacy-карточки вызывайте при Telegram-команде /start <параметр>.
def confirm_target_bot_start(telegram_user_id, start_param):
    return evir_post("/start", {
        "recipientId": str(telegram_user_id),
        "startParam": str(start_param),
    })

Подключение за четыре шага

  1. 1

    Задайте EVIR_API_KEY и EVIR_CONNECTION_ID в переменных окружения сервера.

  2. 2

    В обработчике /offers вызовите POST /next и отправьте каждую полученную карточку.

  3. 3

    Только после успешного ответа Telegram вызовите /served, если формат требует это по таблице ниже.

  4. 4

    Запустите /offers в Telegram и затем нажмите «Проверить подключение» в EVIR.

Что подтверждать после отправки

ФорматВызовКогда засчитывается
Показ/servedПосле успешной отправки
Переход/servedПосле открытия ссылки через EVIR
ОП бота/servedПосле настоящего /start целевого бота
ОП канала/qualifyПосле подтверждения подписки или заявки

Передавайте actionUrl без изменений. Если requiresQualification=true, покажите кнопку «Проверить» и вызовите /qualify с тем же recipientId.

Не отправляйте одну карточку дваждыХраните состояния allocated → sending → sent → acked. Если ответ Telegram неизвестен, не повторяйте отправку. После подтверждённой отправки повторяйте только EVIR /served, пока подтверждение не сохранено.

Ошибки API

401

Ключ неверный или заменён. Создайте новый ключ в карточке бота.

403

SDK_ADS_NOT_TRUSTEDплатные форматы для подключения ещё не включены. Обратитесь в поддержку EVIR.

404

Проверьте ID подключения или deliveryId.

429

Подождите время из заголовка Retry-After.

5xx

Повторите запрос с задержкой и тем же Idempotency-Key.

Лимит: 120 запросов в минуту на подключение. Для сетевых ошибок используйте таймаут и не более нескольких повторов.

assignments: [] — не ошибкаПодходящих карточек сейчас нет или сработал лимит. Покажите «Сейчас предложений нет» и не запускайте бесконечные повторы.
Где получить ID и ключ

Добавьте бота в разделе «Заработок» и выберите API. EVIR покажет ключ один раз, а ID останется в карточке бота. Если бот уже добавлен, откройте его карточку — повторно добавлять его не нужно.

Помощь

Частые вопросы

Почему EVIR не показал ключ?

Ключ появляется один раз после успешного подключения к своему коду. Если окно было закрыто, откройте карточку бота и создайте новый ключ.

Что означает SDK_ADS_NOT_TRUSTED?

Подключение работает, но платные форматы для него ещё не включены. Обратитесь в поддержку EVIR.

Что делать, если ключ EVIR потерян?

Старый ключ повторно посмотреть нельзя. Нажмите «Создать новый ключ EVIR» в карточке бота и сразу сохраните его в секретах сервера. Предыдущий ключ перестанет работать.

Бот уже добавлен. Что делать?

Откройте карточку бота и раздел подключения. Повторно добавлять бота и вводить Telegram-токен не нужно.

Перед запуском

  • EVIR_API_KEY и EVIR_CONNECTION_ID заданы на сервере
  • /offers возвращает или корректно обрабатывает пустой assignments
  • Карточка отправляется один раз, а /served вызывается только после успешной отправки
  • Для ОП канала кнопка «Проверить» вызывает /qualify