SubGram API

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

Всё необходимое для интеграции ваших сервисов с нашей платформой.

Начало работы

API SubGram предоставляет программный доступ к функциям нашей платформы. Вы можете управлять ботами, заказами на рекламу, получать статистику и многое другое.

Все запросы к API следует отправлять на базовый URL:

https://api.subgram.org

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

Для взаимодействия с API требуется ключ аутентификации. Каждый запрос должен содержать HTTP-заголовок Auth с вашим ключом.

Существует три основных типа ключей, каждый для своих задач:

  • Ключ-доступа (Secret Key): 🔐 Ваш главный и секретный ключ для выполнения управляющих действий: создание и обновление заказов (/orders), добавление и настройка ботов (/bots).
    Как получить: в нашем официальном боте @subgram_officialbot перейдите в Профиль → Ключ-доступа (Secret Key). Никогда и никому не передавайте этот ключ!
  • Токен статистики (API Token): 📈 Этот токен предназначен только для получения данных, таких как баланс (/get-balance) и статистика (/statistic).
    Как получить: в боте @subgram_officialbot перейдите в Профиль → Скопировать api token.
  • Ключ бота (API Key): 🤖 Генерируется для каждого добавленного бота. Используется для запросов от имени конкретного бота: получение спонсоров (/get-sponsors) и проверка подписок (/get-user-subscriptions).

Пример заголовка

Auth: ВАШ_СЕКРЕТНЫЙ_КЛЮЧ_ДОСТУПА

Структура ответов

Все ответы от API приходят в формате JSON. Успешные ответы и ошибки имеют стандартизированную структуру для предсказуемой обработки.

Пример успешного ответа (HTTP 200 OK)

{
                      "status": "ok",
                      "code": 200,
                      "message": "Операция прошла успешно",
                      "result": { ... } 
                    }
Примечание: Ключ, содержащий полезные данные, может называться result или response в зависимости от эндпоинта. Всегда проверяйте документацию для конкретного метода.

Пример ответа с ошибкой (HTTP 4xx/5xx)

{
                      "status": "error",
                      "code": 401,
                      "message": "Невалидный API токен"
                    }

Publisher API

Методы для владельцев ботов для монетизации трафика.

Получение спонсоров

POST /get-sponsors

Основной метод для запроса блока обязательной подписки. Сервис анализирует пользователя и подбирает для него наиболее релевантный список спонсоров. Аутентификация происходит по API-ключу бота в заголовке Auth.

Параметры запроса (Body, JSON)

ПараметрТипОбязательныйОписание
chat_idIntegerДаID чата, откуда идет запрос.
user_idIntegerДаID пользователя Telegram.
first_nameStringУсловно*Имя пользователя в Telegram.
usernameStringУсловно*Юзернейм пользователя (без @).
language_codeStringУсловно*Язык интерфейса Telegram у пользователя (например, "ru", "en").
is_premiumBooleanУсловно*Наличие у пользователя Telegram Premium: true или false.
actionStringНет Тип действия:
  • subscribe (по умолчанию): Список спонсоров закрепляется за пользователем.
  • newtask: Список не закрепляется.
genderStringНетПол пользователя:
  • male (мужской)
  • female (женский)
ageInteger/StringНет Возраст пользователя. Можно передать число (25) или категорию:
  • c1: Младше 10
  • c2: 11-13
  • c3: 14-15
  • c4: 16-17
  • c5: 18-24
  • c6: 25 и старше
max_sponsorsIntegerНетМакс. кол-во спонсоров (ссылок на каналы/чаты) в ответе (1-10).
exclude_resource_idsArray[String]НетМассив ID каналов/ботов для исключения. Пример: ["-100123...", "54321..."].
exclude_ads_idsArray[Integer]НетМассив ID заказов для исключения.

* — эти поля становятся обязательными, если ваш бот был добавлен в систему без токена.

Важно: Параметры gender и age следует передавать, только если пользователь явно предоставил эти данные (например, через опрос в боте или форму регистрации). Запрещено угадывать или передавать предполагаемые значения.

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

# Пример тела запроса в формате JSON (cURL)
curl -X POST https://api.subgram.org/get-sponsors \
-H "Auth: ВАШ_API_КЛЮЧ_БОТА" \
-H "Content-Type: application/json" \
-d '{
      "user_id": 123456789,
      "chat_id": 123456789,
      #Параметры ниже если не передавали токен от бота
      #"first_name": "Иван",
      #"username": "ivanov",
      #"language_code": "ru",
      #"is_premium": false,
    }'

В этом режиме SubGram самостоятельно отправляет пользователю блок с обязательной подпиской, если это необходимо. Ваша задача — просто вызвать API и проверить статус. Если статус блокирующий (warning, gender, age, register), больше ничего делать не нужно — сервис все сделает сам.


# --- Файл: utils/subgram_api.py ---
import aiohttp
import logging

API_KEY = "ВАШ_API_КЛЮЧ_БОТА"
URL = "https://api.subgram.org/get-sponsors"
BLOCKING_SUBGRAM_STATUSES = ["warning", "gender", "age", "register"]

async def get_subgram_sponsors(user_id: int, chat_id: int, **kwargs) -> dict | None:
    """Универсальная функция для запроса спонсоров."""
    headers = { "Auth": API_KEY }
    payload = { "user_id": user_id, "chat_id": chat_id }
    payload.update(kwargs)
    
    async with aiohttp.ClientSession() as session:
        try:
            async with session.post(URL, headers=headers, json=payload, timeout=10) as response:
                return await response.json()
        except Exception as e:
            logging.error(f"Ошибка запроса к SubGram API: {e}")
            return None

# --- Файл: handlers/start.py (пример обработчика) ---
from aiogram import F, types, Router
from utils.subgram_api import get_subgram_sponsors, BLOCKING_SUBGRAM_STATUSES
from aiogram.filters import CommandStart

router = Router()

@router.message(CommandStart())
async def handle_start_command(message: types.Message):
    # Вызываем функцию для проверки подписки
    response = await get_subgram_sponsors(
        user_id=message.from_user.id,
        chat_id=message.chat.id
    )

    if not response:
        logging.error(f"Ошибка запроса к SubGram API: Не удалось осуществить запрос")
        # Если API недоступен, выдаем доступ, чтобы не терять пользователя
        await message.answer("✅ Доступ предоставлен.")
        return

    status = response.get("status")

    if status and status in BLOCKING_SUBGRAM_STATUSES:
        #Если бот добавлен по токену и "Получать ссылки в API" - Выкл, SubGram самостоятельно отправит сообщение
        #Вам как владельцу бота делать ничего не нужно
        pass

    else:
        if status and status == "error":
            logging.warning(f"Ошибка SubGram API: {response.get('message')}. Предоставляем доступ.")

        await message.answer("✅ Доступ предоставлен!")
        # ... ваш код для выдачи контента ...
@router.callback_query(F.data.startswith('subgram'))
async def handle_subgram_callbacks(callback: types.CallbackQuery):
    user_id = callback.from_user.id
    chat_id = callback.message.chat.id
    callback_data = callback.data
    
    api_kwargs = {}

    if callback_data == "subgram-op":
        await callback.answer("⏳ Проверяем подписки...")
    elif callback_data.startswith("subgram_"):
        # Универсальная обработка для пола и возраста
        param_type = call.data.split("_")[1]  # 'gender' или 'age'
        param_value = call.data.split("_")[2] # 'male', 'c1', и т.д.
        api_kwargs[param_type] = param_value

    # Отправляем повторный запрос в SubGram API с новыми данными
    response = await get_subgram_sponsors(user_id, chat_id, **api_kwargs)

    # Логика доступа АНАЛОГИЧНА основной

    status = response.get("status")

    if status and status in BLOCKING_SUBGRAM_STATUSES:
        #Если бот добавлен по токену и "Получать ссылки в API" - Выкл, SubGram самостоятельно отправит сообщение, Вам как владельцу бота делать ничего не нужно
        pass

    else:
        if status and status == "error":
            logging.warning(f"Ошибка SubGram API: {response.get('message')}. Предоставляем доступ.")

        await callback.message.answer("✅ Доступ предоставлен!")
        # ... ваш код для выдачи контента ...
            

// --- Файл: subgramApi.js ---
const axios = require('axios');

const API_KEY = 'ВАШ_API_КЛЮЧ_БОТА';
const URL = 'https://api.subgram.org/get-sponsors';
const BLOCKING_SUBGRAM_STATUSES = ["warning", "gender", "age", "register"];

async function getSubgramSponsors(userId, chatId, options = {}) {
  const headers = { 
      'Auth': API_KEY,
      'Content-Type': 'application/json'
  };
  const payload = { user_id: userId, chat_id: chatId, ...options };
  
  try {
    const response = await axios.post(URL, payload, { headers, timeout: 10000 });
    return response.data;
  } catch (error) {
    console.error('Ошибка запроса к SubGram API:', error.response?.data || error.message);
    return null;
  }
}
module.exports = { getSubgramSponsors, BLOCKING_SUBGRAM_STATUSES };


// --- Файл: bot.js (пример с node-telegram-bot-api) ---
// const TelegramBot = require('node-telegram-bot-api');
// const token = 'ВАШ_ТЕЛЕГРАМ_ТОКЕН';
// const bot = new TelegramBot(token, { polling: true });
// const { getSubgramSponsors, BLOCKING_SUBGRAM_STATUSES } = require('./subgramApi');

bot.onText(/\/start/, async (msg) => {
    // Вызываем функцию для проверки подписки
    const response = await getSubgramSponsors(
        msg.from.id,
        msg.chat.id
    );

    if (!response) {
        console.error("Ошибка запроса к SubGram API: Не удалось осуществить запрос");
        // Если API недоступен, выдаем доступ, чтобы не терять пользователя
        await bot.sendMessage(msg.chat.id, "✅ Доступ предоставлен.");
        return;
    }

    const status = response.status;

    if (status && BLOCKING_SUBGRAM_STATUSES.includes(status)) {
        //Если бот добавлен по токену и "Получать ссылки в API" - Выкл, SubGram самостоятельно отправит сообщение
        //Вам как владельцу бота делать ничего не нужно
    } else {
        if (status && status === "error") {
            console.warn(`Ошибка SubGram API: ${response.message}. Предоставляем доступ.`);
        }
        await bot.sendMessage(msg.chat.id, "✅ Доступ предоставлен!");
        // ... ваш код для выдачи контента ...
    }
});

bot.on('callback_query', async (callbackQuery) => {
    if (!callbackQuery.data.startsWith('subgram')) {
        return;
    }

    const userId = callbackQuery.from.id;
    const chatId = callbackQuery.message.chat.id;
    const callbackData = callbackQuery.data;

    const api_kwargs = {};
    
    if (callbackData === "subgram-op") {
        await bot.answerCallbackQuery(callbackQuery.id, { text: "⏳ Проверяем подписки..." });
    } else if (callbackData.startsWith("subgram_")) {
        // Универсальная обработка для пола и возраста
        const parts = callbackData.split("_");
        const param_type = parts[1]; // 'gender' или 'age'
        const param_value = parts[2]; // 'male', 'c1', и т.д.
        api_kwargs[param_type] = param_value;
    }

    // Отправляем повторный запрос в SubGram API с новыми данными
    const response = await getSubgramSponsors(userId, chatId, api_kwargs);

    // Логика доступа АНАЛОГИЧНА основной
    if (!response) {
        console.error("Ошибка запроса к SubGram API: Не удалось осуществить запрос");
        await bot.sendMessage(chatId, "✅ Доступ предоставлен.");
        return;
    }
    
    const status = response.status;

    if (status && BLOCKING_SUBGRAM_STATUSES.includes(status)) {
        //Если бот добавлен по токену и "Получать ссылки в API" - Выкл, SubGram самостоятельно отправит сообщение, Вам как владельцу бота делать ничего не нужно
    } else {
        if (status && status === "error") {
             console.warn(`Ошибка SubGram API: ${response.message}. Предоставляем доступ.`);
        }
        await bot.sendMessage(chatId, "✅ Доступ предоставлен!");
        // ... ваш код для выдачи контента ...
    }
});
                        

<?php
// --- Файл: subgram_api.php ---
const API_KEY = "ВАШ_API_КЛЮЧ_БОТА";
const URL = "https://api.subgram.org/get-sponsors";
const BLOCKING_SUBGRAM_STATUSES = ["warning", "gender", "age", "register"];

function getSubgramSponsors($userId, $chatId, $options = []) {
    $payload = ['user_id' => $userId, 'chat_id' => $chatId];
    $payload = array_merge($payload, $options);
    
    $headers = [
        'Auth: ' . API_KEY,
        'Content-Type: application/json'
    ];
    
    $ch = curl_init(URL);
    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
    curl_setopt($ch, CURLOPT_TIMEOUT, 10);
    
    $response_body = curl_exec($ch);
    $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);
    
    if ($response_body === false) {
        error_log("Ошибка запроса к SubGram API: cURL error");
        return null;
    }
    
    return json_decode($response_body, true);
}

// --- Файл: bot.php (пример обработчика вебхука) ---
// require_once 'subgram_api.php';
// ... (здесь должны быть ваши функции для отправки запросов к Telegram API, например, sendMessage, answerCallbackQuery) ...

$update = json_decode(file_get_contents('php://input'), true);

if (isset($update['message'])) {
    $userId = $update['message']['from']['id'];
    $chatId = $update['message']['chat']['id'];

    // Вызываем функцию для проверки подписки
    $response = getSubgramSponsors($userId, $chatId);

    if ($response === null) {
        error_log("Ошибка запроса к SubGram API: Не удалось осуществить запрос");
        // Если API недоступен, выдаем доступ, чтобы не терять пользователя
        sendMessage($chatId, "✅ Доступ предоставлен.");
        exit();
    }
    
    $status = $response['status'] ?? null;
    
    if ($status && in_array($status, BLOCKING_SUBGRAM_STATUSES)) {
        //Если бот добавлен по токену и "Получать ссылки в API" - Выкл, SubGram самостоятельно отправит сообщение
        //Вам как владельцу бота делать ничего не нужно
    } else {
        if ($status && $status === "error") {
            error_log("Ошибка SubGram API: " . $response['message'] . ". Предоставляем доступ.");
        }
        sendMessage($chatId, "✅ Доступ предоставлен!");
        // ... ваш код для выдачи контента ...
    }

} elseif (isset($update['callback_query'])) {
    $callback = $update['callback_query'];
    $data = $callback['data'];

    if (strpos($data, 'subgram') === 0) {
        $userId = $callback['from']['id'];
        $chatId = $callback['message']['chat']['id'];
        
        $api_kwargs = [];

        if ($data === "subgram-op") {
            answerCallbackQuery($callback['id'], ['text' => '⏳ Проверяем подписки...']);
        } elseif (strpos($data, 'subgram_') === 0) {
            // Универсальная обработка для пола и возраста
            $parts = explode('_', $data);
            $param_type = $parts[1]; // 'gender' или 'age'
            $param_value = $parts[2]; // 'male', 'c1', и т.д.
            $api_kwargs[$param_type] = $param_value;
        }

        // Отправляем повторный запрос в SubGram API с новыми данными
        $response = getSubgramSponsors($userId, $chatId, $api_kwargs);
        
        // Логика доступа АНАЛОГИЧНА основной
        if ($response === null) {
             error_log("Ошибка запроса к SubGram API: Не удалось осуществить запрос");
             sendMessage($chatId, "✅ Доступ предоставлен.");
             exit();
        }

        $status = $response['status'] ?? null;

        if ($status && in_array($status, BLOCKING_SUBGRAM_STATUSES)) {
            //Если бот добавлен по токену и "Получать ссылки в API" - Выкл, SubGram самостоятельно отправит сообщение, Вам как владельцу бота делать ничего не нужно
        } else {
            if ($status && $status === "error") {
                 error_log("Ошибка SubGram API: " . $response['message'] . ". Предоставляем доступ.");
            }
            sendMessage($chatId, "✅ Доступ предоставлен!");
            // ... ваш код для выдачи контента ...
        }
    }
}
?>
                        

В этом режиме сервис в ответ на ваш запрос отдает JSON с данными, на основе которых вы должны самостоятельно сформировать и отправить пользователю сообщение с кнопками. Этот подход требует обработки всех возможных статусов ответа: показ спонсоров, вопросы о поле/возрасте и анкету.


# --- Файл: utils/subgram_api.py ---
import aiohttp
import logging

API_KEY = "ВАШ_API_КЛЮЧ_БОТА"
URL = "https://api.subgram.org/get-sponsors"
BLOCKING_SUBGRAM_STATUSES = ["warning", "gender", "age", "register"]

async def get_subgram_sponsors(user_id: int, chat_id: int, **kwargs) -> dict | None:
    """Универсальная функция для запроса спонсоров."""
    headers = { "Auth": API_KEY }
    payload = { "user_id": user_id, "chat_id": chat_id }
    payload.update(kwargs)
    
    async with aiohttp.ClientSession() as session:
        try:
            async with session.post(URL, headers=headers, json=payload, timeout=10) as response:
                return await response.json()
        except Exception as e:
            logging.error(f"Ошибка запроса к SubGram API: {e}")
            return None

# --- Файл: handlers/subgram.py ---
import logging
from aiogram import F, types, Router
from aiogram.filters import CommandStart
from aiogram.utils.keyboard import InlineKeyboardBuilder
from aiogram.exceptions import TelegramBadRequest

from utils.subgram_api import get_subgram_sponsors, BLOCKING_SUBGRAM_STATUSES

router = Router()

async def process_subgram_check(user: types.User, chat_id: int, api_kwargs: dict = None):
    """Основная функция для обработки всех статусов от SubGram."""
    if api_kwargs is None:
        api_kwargs = {}

    # 1. Передаем больше данных о пользователе, если не передавали токен. Если передавали - не требуется создавать словарь и передавать его
    user_data = {
        "first_name": user.first_name,
        "username": user.username,
        "language_code": user.language_code,
        "is_premium": bool(user.is_premium)
    }
    user_data.update(api_kwargs)
    
    response = await get_subgram_sponsors(user_id=user.id, chat_id=chat_id, **user_data)

    if not response:
        return True

    status = response.get("status")
    builder = InlineKeyboardBuilder()

    if status == "warning":
        text = "Пожалуйста, выполните задания ниже:"
        sponsors = response.get("additional", {}).get("sponsors", [])
        for sponsor in sponsors:
            # 2. Показываем только тех, на кого надо подписаться
            if sponsor.get("available_now") and sponsor.get("status") == "unsubscribed":
                builder.button(text=sponsor.get("button_text", "Подписаться"), url=sponsor.get("link"))
        builder.button(text="✅ Я выполнил", callback_data="subgram-op")
        builder.adjust(1)

    elif status == "gender":
        # 3. Обработка вопроса о поле
        text = "Укажите ваш пол:"
        builder.button(text="Мужской", callback_data="subgram_gender_male")
        builder.button(text="Женский", callback_data="subgram_gender_female")
        builder.adjust(2)

    elif status == "age":
        # 3. Обработка вопроса о возрасте
        text = "Укажите ваш возраст:"
        # Категории возраста из документации
        age_categories = {"c1": "Младше 10", "c2": "11-13", "c3": "14-15", "c4": "16-17", "c5": "18-24", "c6": "25 и старше"}
        for code, age_text in age_categories.items():
            builder.button(text=age_text, callback_data=f"subgram_age_{code}")
        builder.adjust(2)

    elif status == "register":
        # 4. Обработка анкеты
        text = "Для продолжения, пожалуйста, пройдите быструю регистрацию."
        reg_url = response.get("additional", {}).get("registration_url")
        if reg_url:
            builder.button(text="✅ Пройти регистрацию", web_app=types.WebAppInfo(url=reg_url))
            builder.button(text="Продолжить", callback_data="subgram-op")
            builder.adjust(1)
        else:
            return True
            
    else: # error или неизвестный статус
        return True

    await bot.send_message(chat_id, text, reply_markup=builder.as_markup())

    return False


@router.message(CommandStart())
async def handle_start_links(message: types.Message):
    response = await process_subgram_check(message.from_user, message.chat.id)

    if response:
        # Даем доступ
        await bot.send_message(chat_id, "✅ Доступ предоставлен!")
        # ... ваш основной код ...
    


@router.callback_query(F.data.startswith("subgram"))
async def handle_callback_links(callback: types.CallbackQuery):
    # 5. Удаляем старое сообщение
    try:
        await callback.message.delete()
    except TelegramBadRequest:
        logging.info("Не удалось удалить сообщение (возможно, уже удалено)")

    api_kwargs = {}
    data = callback.data

    # 6. Парсим callback_data
    if data.startswith("subgram_gender_"):
        api_kwargs["gender"] = data.split("_")[2]
    elif data.startswith("subgram_age_"):
        api_kwargs["age"] = data.split("_")[2]
    # Для subgram-op (Я выполнил/Продолжить) api_kwargs остается пустым
    
    await callback.answer()
    response = await process_subgram_check(callback.from_user, callback.message.chat.id, api_kwargs)

    if response:
        # Даем доступ
        await callback.message.answer("✅ Доступ предоставлен!")
        # ... ваш основной код ...
                        

// --- Файл: subgramApi.js ---
const axios = require('axios');

const API_KEY = 'ВАШ_API_КЛЮЧ_БОТА';
const URL = 'https://api.subgram.org/get-sponsors';

async function getSubgramSponsors(userId, chatId, options = {}) {
  const headers = { 
      'Auth': API_KEY,
      'Content-Type': 'application/json'
  };
  const payload = { user_id: userId, chat_id: chatId, ...options };
  
  try {
    const response = await axios.post(URL, payload, { headers, timeout: 10000 });
    return response.data;
  } catch (error) {
    console.error('Ошибка запроса к SubGram API:', error.response?.data || error.message);
    return null;
  }
}
module.exports = { getSubgramSponsors };


// --- Файл: bot.js (пример с node-telegram-bot-api) ---
// const TelegramBot = require('node-telegram-bot-api');
// const token = 'ВАШ_ТЕЛЕГРАМ_ТОКЕН';
// const bot = new TelegramBot(token, { polling: true });
// const { getSubgramSponsors } = require('./subgramApi');

/**
 * Основная функция для обработки всех статусов от SubGram.
 * @returns {Promise} Возвращает true, если нужно дать доступ, иначе false.
 */
async function processSubgramCheck(user, chatId, apiOptions = {}) {
    // 1. Передаем больше данных о пользователе, если не передавали токен.
    const userData = {
        "first_name": user.first_name,
        "username": user.username,
        "language_code": user.language_code,
        "is_premium": !!user.is_premium
    };
    Object.assign(userData, apiOptions);

    const response = await getSubgramSponsors(user.id, chatId, userData);

    if (!response) {
        return true; // Дать доступ при ошибке API
    }

    const status = response.status;
    let text = "";
    const inline_keyboard = [];

    if (status === "warning") {
        text = "Пожалуйста, выполните задания ниже:";
        const sponsors = response.additional?.sponsors || [];
        // 2. Показываем только тех, на кого надо подписаться
        sponsors.forEach(sponsor => {
            if (sponsor.available_now && sponsor.status === "unsubscribed") {
                inline_keyboard.push([{ text: sponsor.button_text || "Подписаться", url: sponsor.link }]);
            }
        });
        inline_keyboard.push([{ text: "✅ Я выполнил", callback_data: "subgram-op" }]);

    } else if (status === "gender") {
        // 3. Обработка вопроса о поле
        text = "Укажите ваш пол:";
        inline_keyboard.push([
            { text: "Мужской", callback_data: "subgram_gender_male" },
            { text: "Женский", callback_data: "subgram_gender_female" }
        ]);

    } else if (status === "age") {
        // 3. Обработка вопроса о возрасте
        text = "Укажите ваш возраст:";
        const age_categories = {"c1": "Младше 10", "c2": "11-13", "c3": "14-15", "c4": "16-17", "c5": "18-24", "c6": "25 и старше"};
        // Группируем по 2 кнопки в ряд
        const rows = [];
        const keys = Object.keys(age_categories);
        for (let i = 0; i < keys.length; i += 2) {
            const row = [];
            row.push({ text: age_categories[keys[i]], callback_data: `subgram_age_${keys[i]}`});
            if (keys[i+1]) {
                row.push({ text: age_categories[keys[i+1]], callback_data: `subgram_age_${keys[i+1]}`});
            }
            rows.push(row);
        }
        inline_keyboard.push(...rows);

    } else if (status === "register") {
        // 4. Обработка анкеты
        text = "Для продолжения, пожалуйста, пройдите быструю регистрацию.";
        const reg_url = response.additional?.registration_url;
        if (reg_url) {
            inline_keyboard.push([{ text: "✅ Пройти регистрацию", web_app: { url: reg_url } }]);
            inline_keyboard.push([{ text: "Продолжить", callback_data: "subgram-op" }]);
        } else {
            return true; // Дать доступ, если URL не пришел
        }
    
    } else { // error или неизвестный статус
        return true; // Дать доступ
    }

    await bot.sendMessage(chatId, text, { reply_markup: { inline_keyboard } });
    return false; // Не давать доступ, т.к. отправили сообщение с заданием
}


bot.onText(/\/start/, async (msg) => {
    const shouldGrantAccess = await processSubgramCheck(msg.from, msg.chat.id);
    if (shouldGrantAccess) {
        // Даем доступ
        await bot.sendMessage(msg.chat.id, "✅ Доступ предоставлен!");
        // ... ваш основной код ...
    }
});

bot.on('callback_query', async (callbackQuery) => {
    if (!callbackQuery.data.startsWith("subgram")) {
        return;
    }

    // 5. Удаляем старое сообщение
    bot.deleteMessage(callbackQuery.message.chat.id, callbackQuery.message.message_id)
        .catch(err => console.log("Не удалось удалить сообщение (возможно, уже удалено)"));

    const api_kwargs = {};
    const data = callbackQuery.data;

    // 6. Парсим callback_data
    if (data.startsWith("subgram_gender_")) {
        api_kwargs["gender"] = data.split("_")[2];
    } else if (data.startsWith("subgram_age_")) {
        api_kwargs["age"] = data.split("_")[2];
    }
    // Для subgram-op (Я выполнил/Продолжить) api_kwargs остается пустым

    await bot.answerCallbackQuery(callbackQuery.id);
    const shouldGrantAccess = await processSubgramCheck(callbackQuery.from, callbackQuery.message.chat.id, api_kwargs);

    if (shouldGrantAccess) {
        // Даем доступ
        await bot.sendMessage(callbackQuery.message.chat.id, "✅ Доступ предоставлен!");
        // ... ваш основной код ...
    }
});
                        

<?php
// --- Файл: subgram_api.php ---
const API_KEY = "ВАШ_API_КЛЮЧ_БОТА";
const URL = "https://api.subgram.org/get-sponsors";

function getSubgramSponsors($userId, $chatId, $options = []) {
    $payload = ['user_id' => $userId, 'chat_id' => $chatId];
    $payload = array_merge($payload, $options);
    
    $headers = [
        'Auth: ' . API_KEY,
        'Content-Type: application/json'
    ];
    
    $ch = curl_init(URL);
    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
    curl_setopt($ch, CURLOPT_TIMEOUT, 10);
    
    $response_body = curl_exec($ch);
    curl_close($ch);
    
    if ($response_body === false) {
        error_log("Ошибка запроса к SubGram API: cURL error");
        return null;
    }
    
    return json_decode($response_body, true);
}


// --- Файл: bot.php (пример обработчика вебхука) ---
// require_once 'subgram_api.php';
// ... (здесь должны быть ваши функции для отправки запросов к Telegram API, например, sendMessage, deleteMessage, answerCallbackQuery) ...

/**
 * Основная функция для обработки всех статусов от SubGram.
 * @return bool Возвращает true, если нужно дать доступ, иначе false.
 */
function processSubgramCheck($user, $chatId, $apiOptions = []) {
    // 1. Передаем больше данных о пользователе, если не передавали токен.
    $userData = [
        "first_name" => $user['first_name'],
        "username" => $user['username'] ?? null,
        "language_code" => $user['language_code'] ?? null,
        "is_premium" => !empty($user['is_premium'])
    ];
    $userData = array_merge($userData, $apiOptions);
    
    $response = getSubgramSponsors($user['id'], $chatId, $userData);

    if ($response === null) {
        return true; // Дать доступ при ошибке API
    }

    $status = $response['status'] ?? null;
    $keyboard = [];
    $text = "";

    if ($status === "warning") {
        $text = "Пожалуйста, выполните задания ниже:";
        $sponsors = $response['additional']['sponsors'] ?? [];
        // 2. Показываем только тех, на кого надо подписаться
        foreach ($sponsors as $sponsor) {
            if (!empty($sponsor['available_now']) && $sponsor['status'] === "unsubscribed") {
                $keyboard[] = [['text' => $sponsor['button_text'] ?? "Подписаться", 'url' => $sponsor['link']]];
            }
        }
        $keyboard[] = [['text' => "✅ Я выполнил", 'callback_data' => "subgram-op"]];
    
    } elseif ($status === "gender") {
        // 3. Обработка вопроса о поле
        $text = "Укажите ваш пол:";
        $keyboard[] = [
            ['text' => "Мужской", 'callback_data' => "subgram_gender_male"],
            ['text' => "Женский", 'callback_data' => "subgram_gender_female"]
        ];

    } elseif ($status === "age") {
        // 3. Обработка вопроса о возрасте
        $text = "Укажите ваш возраст:";
        $age_categories = ["c1" => "Младше 10", "c2" => "11-13", "c3" => "14-15", "c4" => "16-17", "c5" => "18-24", "c6" => "25 и старше"];
        $row = [];
        foreach ($age_categories as $code => $age_text) {
            $row[] = ['text' => $age_text, 'callback_data' => "subgram_age_{$code}"];
            if (count($row) == 2) {
                $keyboard[] = $row;
                $row = [];
            }
        }
        if (!empty($row)) {
            $keyboard[] = $row;
        }

    } elseif ($status === "register") {
        // 4. Обработка анкеты
        $text = "Для продолжения, пожалуйста, пройдите быструю регистрацию.";
        $reg_url = $response['additional']['registration_url'] ?? null;
        if ($reg_url) {
            $keyboard[] = [['text' => "✅ Пройти регистрацию", 'web_app' => ['url' => $reg_url]]];
            $keyboard[] = [['text' => "Продолжить", 'callback_data' => "subgram-op"]];
        } else {
            return true; // Дать доступ, если URL не пришел
        }
    
    } else { // error или неизвестный статус
        return true; // Дать доступ
    }

    sendMessage($chatId, $text, json_encode(['inline_keyboard' => $keyboard]));
    return false; // Не давать доступ, т.к. отправили сообщение с заданием
}


$update = json_decode(file_get_contents('php://input'), true);

if (isset($update['message'])) {
    $shouldGrantAccess = processSubgramCheck($update['message']['from'], $update['message']['chat']['id']);
    if ($shouldGrantAccess) {
        // Даем доступ
        sendMessage($update['message']['chat']['id'], "✅ Доступ предоставлен!");
        // ... ваш основной код ...
    }
} elseif (isset($update['callback_query'])) {
    $callback = $update['callback_query'];
    $data = $callback['data'];

    if (strpos($data, "subgram") === 0) {
        // 5. Удаляем старое сообщение
        deleteMessage($callback['message']['chat']['id'], $callback['message']['message_id']);

        $api_kwargs = [];
        // 6. Парсим callback_data
        if (strpos($data, "subgram_gender_") === 0) {
            $api_kwargs["gender"] = explode("_", $data)[2];
        } elseif (strpos($data, "subgram_age_") === 0) {
            $api_kwargs["age"] = explode("_", $data)[2];
        }
        // Для subgram-op (Я выполнил/Продолжить) api_kwargs остается пустым

        answerCallbackQuery($callback['id']);
        $shouldGrantAccess = processSubgramCheck($callback['from'], $callback['message']['chat']['id'], $api_kwargs);

        if ($shouldGrantAccess) {
            // Даем доступ
            sendMessage($callback['message']['chat']['id'], "✅ Доступ предоставлен!");
            // ... ваш основной код ...
        }
    }
}
?>
                        

Описание ответа

Сервер обрабатывает ваш запрос и возвращает JSON, на основе которого вы должны строить логику в боте. Возможны следующие варианты:

Статус ответаHTTP КодЧто делать
ok200Можно пропускать пользователя. Это означает, что он либо уже подписан на все ресурсы, либо для него не нашлось подходящих спонсоров (HTTP код ответа при этом будет 404).
warning200Не пропускать. Пользователю нужно выполнить действие (например, подписаться на канал).
register200Не пропускать. Требуется показать анкету. В поле additional.registration_url будет ссылка для кнопки WebApp.
gender / age200Не пропускать. Сервис не смог определить пол/возраст. Требуется отправить пользователю кнопки с вариантами и при повторном запросе передать его выбор.
error4xx/5xxПроизошла ошибка (неверный API-ключ, ошибка сервера и т.д.). Сообщение будет в поле message.

Ответ со статусом register (Анкета Web App)

Если для пользователя требуется сбор дополнительной информации (пол/возраст) и в настройках бота включена опция "Показывать анкету", вы получите следующий ответ. Ваша задача — создать inline-кнопку, которая откроет эту ссылку как Web App.

Подробнее о том, как выглядит и работает анкета, можно прочитать в нашей статье: Что такое анкета?
Пример ответа

{
    "status": "register",
    "code": 200,
    "message": "Требуется дополнительная информация о пользователе.",
    "additional": {
        "registration_url": "https://api.subgram.org/user-form?token=gYRNST3HF1"
    }
}
                        
Пример создания WebApp кнопки на Aiogram (если не добавляли токен от бота или "Получать ссылки в API" вкл.)
Примечание: Если Вы предоставляли токен от бота и "Получать ссылки в API" - Выкл, то НИЧЕГО ИЗМЕНЯТЬ/ДОПОЛНЯТЬ НЕ НУЖНО, все работает как на старой версии API, так и на новой (иными словами - "под ключ")

from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton, WebAppInfo
from aiogram.utils.keyboard import InlineKeyboardBuilder

# response - это ваш JSON-ответ от API со статусом "register"
reg_url = response['additional']['registration_url']

builder = InlineKeyboardBuilder()

# Кнопка для открытия WebApp
builder.button(
    text="✅ Пройти быструю регистрацию",
    web_app=WebAppInfo(url=reg_url)
)
# Кнопка для повторной проверки ПОСЛЕ заполнения анкеты
builder.button(
    text="Продолжить",
    callback_data="subgram-op" # тот же колбэк, что и у "Я выполнил"
)
builder.adjust(1)

await message.answer(
    "Для продолжения, пожалуйста, укажите ваш пол и возраст.", 
    reply_markup=builder.as_markup()
)
                        

Структура объекта в массиве sponsors

ПолеТипОписание
ads_idStringУникальный ID рекламной кампании.
linkStringГотовая ссылка для подписки/перехода.
resource_idStringУникальный ID ресурса (канала или бота) в Telegram.
typeStringТип ресурса:
  • channel: Канал или чат.
  • bot: Telegram бот.
  • resource: Внешний ресурс (miniapp, сайт) или канал/бот, для которого не требуется проверка подписки.
statusString Текущий статус подписки:
  • subscribed: Подписан.
  • unsubscribed: Не подписан.
  • notgetted: Пользователь подписан на канал, ему можно дать доступ к контенту, однако он уже был в канале ранее и отписался. Telegram не засчитал его как уникального подписчика и не учел вступление по пригласительной ссылке (не отображается в статистике по ней). В данных ситуациях мы не берем деньги с рекламодателя и, соответственно, не выплачиваем владельцу бота
available_nowBooleanФлаг, активен ли спонсор в данный момент. Если false, его не следует показывать.
button_textStringРекомендуемый текст для кнопки (например "Подписаться").
resource_logoStringURL на логотип ресурса. Может быть пустой строкой.
resource_nameStringНазвание ресурса (канала, бота).

Управление ботами

POST /bots

Единый эндпоинт для добавления, обновления и получения информации о ваших ботах. Аутентификация происходит по вашему ключу-доступа (Secret Key), переданному в заголовке Auth.
В теле запроса обязательно должно быть поле action, которое определяет действие.

Action: add — Добавление нового бота

Регистрирует нового бота в системе и возвращает его API-ключ.

ПараметрТипОбязательныйОписание
actionStringДаВсегда "add".
bot_tokenStringУсловно*Токен бота от @BotFather.
bot_idIntegerУсловно*ID бота. Обязателен, если нет bot_token.
bot_nameStringУсловно*Имя бота. Обязателен, если нет bot_token.
bot_nicknameStringУсловно*Юзернейм бота (без @). Обязателен, если нет bot_token.
*Необходимо указать либо bot_token, либо набор bot_id, bot_name, bot_nickname.
time_purgeIntegerНетВремя (в минутах), на которое будет закеширован список спонсоров, подобранный для пользователя. По умолч.: 180. (5-4320).
max_sponsorsIntegerНетКол-во спонсоров, которое сервис будет выдавать при запросе пользователю. По умолч.: 4. (1-10).
get_linksIntegerНетПолучать только ссылки (1) или позволить SubGram отправлять блок ОП самостоятельно (0). По умолч.: 0.
show_quizIntegerНетПоказывать Web App анкету для сбора пола/возраста (1) или нет (0). По умолч.: 1.
gender_questionIntegerНетЗадавать ли вопрос о поле, если он не определен (1 - да, 0 - нет). По умолч.: 1.
age_questionIntegerНетЗадавать ли вопрос о возрасте, если он не определен (1 - да, 0 - нет). По умолч.: 0.
text_opStringНетКастомный текст для блока ОП. Укажите null для сброса на стандартный текст.
image_opStringНетURL изображения для блока ОП. null для удаления картинки.
forbidden_themesArray[String]НетМассив кодов категорий рекламы для исключения.
Актуальный список кодов можно получить из эндпоинта /filters (в объекте filters.bots.forbidden_themes).
Пример запроса (add)
{
                      "action": "add",
                      "bot_token": "123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11",
                      "max_sponsors": 3,
                      "text_op": "Для доступа к эксклюзиву, подпишись:",
                      "forbidden_themes": ["crypto", "games"]
                    }
Пример ответа (add)
{
                      "status": "ok",
                      "message": "Бот успешно добавлен.",
                      "result": { "api_key": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4" }
                    }

Action: update — Обновление бота

Изменяет настройки существующего бота.

ПараметрТипОбязательныйОписание
actionStringДаВсегда "update".
bot_idIntegerДаID бота, настройки которого нужно изменить.
is_onIntegerНетВключить (1) или выключить (0) бота в системе SubGram.
Все остальные параметры из действия add не обязательны. Передавайте только те, которые хотите изменить.
Пример запроса (update)
{
                      "action": "update",
                      "bot_id": 123456789,
                      "is_on": 0,
                      "time_purge": 60
                    }

Action: info — Получение информации

Возвращает полную информацию и настройки по указанному боту.

ПараметрТипОбязательныйОписание
actionStringДаВсегда "info".
bot_idIntegerДаID бота, информацию о котором нужно получить.
Пример ответа (info)
{
                        "status": "ok",
                        "message": "Информация о боте получена.",
                        "result": {
                            "bot_id": 123456789,
                            "bot_name": "Мой Супер Бот",
                            "bot_nickname": "MySuperBot",
                            "status": "active",
                            "note": null,
                            "is_on": 1,
                            "profit": 12345.67,
                            "profit_own_orders": 123.45,
                            "api_key": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4",
                            "time_purge": 180,
                            "max_sponsors": 4,
                            "get_links": 0,
                            "gender_question": 1,
                            "age_question": 0,
                            "show_quiz": 1,
                            "text_op": "Для доступа к эксклюзиву, подпишись:",
                            "image_op": "https://api.subgram.org/image-op/some-image.jpg",
                            "forbidden_themes": ["crypto", "games"]
                        }
                    }

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

POST /get-user-subscriptions

Позволяет проверить статус подписки конкретного пользователя на один или несколько ресурсов. Аутентификация по API-ключу бота.

Параметры запроса (Body, JSON)

ПараметрТипОбязательныйОписание
user_idIntegerДаID пользователя Telegram, чьи подписки нужно проверить.
linksArray[String]НетМассив ссылок, статус подписки на которые нужно проверить.
start_dateStringНетНачальная дата для выборки в формате YYYY-MM-DD.
end_dateStringНетКонечная дата для выборки в формате YYYY-MM-DD.
Примечание: Если не передать ни links, ни даты, метод вернет все подписки пользователя за последние 30 дней.

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

{
                      "user_id": 123456789,
                      "links": [
                        "https://t.me/+AbCdEfGhIjKlMnOp",
                        "https://t.me/some_bot"
                      ]
                    }

Пример успешного ответа (HTTP 200)

{
                      "status": "ok",
                      "code": 200,
                      "message": "Информация о подписках получена",
                      "additional": {
                        "sponsors": [
                          {
                            "ads_id": "123",
                            "link": "https://t.me/+AbCdEfGhIjKlMnOp",
                            "resource_id": "-100123456789",
                            "type": "channel",
                            "status": "subscribed",
                            "available_now": true,
                            "button_text": "Подписаться",
                            "resource_logo": "https://img.subgram.ru/...",
                            "resource_name": "Название Канала"
                          },
                          {
                            "ads_id": "124",
                            "link": "https://t.me/some_bot?start=ref",
                            "resource_id": "987654321",
                            "type": "bot",
                            "status": "unsubscribed",
                            "available_now": true,
                            "button_text": "Запустить бота",
                            "resource_logo": "",
                            "resource_name": "Какой-то бот"
                          }
                        ]
                      }
                    }

Advertiser API

Методы для рекламодателей для управления кампаниями.

Управление заказами

POST /orders

Единый эндпоинт для создания, обновления и получения информации о ваших заказах (рекламных кампаниях). Аутентификация происходит по вашему ключу-доступа (Secret Key), переданному в заголовке Auth.
В теле запроса обязательно должно быть поле action, которое определяет действие.

Action: create — Создание заказа

Создает новую рекламную кампанию и отправляет ее на модерацию.

ПараметрТипОбязательныйОписание
actionStringДаВсегда "create".
linkStringДаПолная ссылка на ресурс (https://t.me/...).
ads_typeStringДаТип заказа: channel, bot или resource.
nameStringНетНазвание заказа. По умолч.: "Новый заказ".
is_onIntegerНетЗапустить ли заказ сразу после модерации (1) или оставить на паузе (0). По умолч.: 1.
quantity_allIntegerДаОбщее желаемое количество подписчиков (10-10,000,000).
quantity_dayIntegerНетДневной лимит подписчиков. По умолч. равен quantity_all.
priceFloatНетБазовая цена за обычного подписчика (до применения коэффициентов). Если не указана, будет установлена стандартная.
price_premiumFloatНетБазовая цена за премиум-подписчика. Применяется, только если в user_parameters указан only_premium: 1.
bot_tokenStringУсловно*Токен бота или код от BotMembers. *Обязателен, если ads_type = bot.
to_bot_memberIntegerНетТип токена для бота: 0 = стандартный, 1 = код от BotMembers. По умолч.: 0.
track_unsubscriptionsBooleanНетОтслеживать отписки (true) или нет (false). Применимо только для ads_type = channel. По умолч.: true.
is_liteIntegerНетАктивировать плавное распределение вступлений в течение дня (1) или получать подписчиков максимально быстро (0). По умолч.: 0.
sub_speedIntegerНетСкорость вступлений в час. Имеет смысл только если is_lite: 1.
user_parametersObjectНетОбъект с параметрами таргетинга. Описание полей см. в таблице ниже.
forbidden_themesArray[String]НетМассив кодов категорий ботов для исключения. Коды можно получить из /filters.
order_scheduleObjectНетОбъект для настройки расписания показов. Описание полей см. ниже.
Структура объекта user_parameters
ПараметрТипОписание и Коэффициенты
genderStringПол: male, female, all. По умолч.: "all".
only_premiumIntegerТаргетинг по Premium: 0 - любой, 1 - только Premium, 2 - без Premium. По умолч.: 0.
languagesArray[Integer]Массив ID языков для таргетинга. ID можно получить из /filters. По умолч.: [1,2,3,4,5] ( то есть Русский, Узбекский, Казахский, Украинский, Белорусский). Передайте null для таргетинга на все языки.
countriesArray[Integer]Массив ID стран. ID можно получить из /filters. Коэффициент к цене: x2.5
citiesArray[Integer]Массив ID городов. ID можно получить из /filters. Коэффициент к цене: x2.5
devicestypeArray[String]Массив типов устройств (напр. ["desktop", "mobile"]). ID можно получить из /filters. Коэффициент к цене: x1.5
devicesosArray[Integer]Массив ID операционных систем. ID можно получить из /filters. Коэффициент к цене: x1.5
agesArray[Integer] Массив ID категорий возраста. ID можно получить из /filters.
  • Младше 10: x1.5
  • 11-13: x1.5
  • 14-15: x2.0
  • 16-17: x2.0
  • 18-24: x2.5
  • 25 и старше: x3.0
При выборе нескольких категорий применяется формула:
Сумма коэффициентов / (Количество категорий * 0.9)
has_photoIntegerТребовать наличие фото в профиле (1 - да, 0 - не важно). Коэффициент к цене: x1.1
has_usernameIntegerТребовать наличие юзернейма (1 - да, 0 - не важно). Коэффициент к цене: x1.1
has_bioIntegerТребовать наличие описания профиля (1 - да, 0 - не важно). Коэффициент к цене: x1.1
has_first_nameIntegerТребовать наличие имени (1 - да, 0 - не важно). Коэффициент к цене: x1.1
has_ru_nameIntegerТребовать наличие ру имени (1 - да, 0 - не важно). Коэффициент к цене: x1.1
has_fake_checkIntegerТребовать прохождение анти-фрод проверки (1 - да, 0 - не важно). Коэффициент к цене: x1.1
old_accountIntegerМинимальный возраст аккаунта: 1 (≥2 лет, x1.1), 2 (≥3 лет, x1.2), 3 (≥5 лет, x1.3), 4 (≥7 лет, x1.4), 5 (≥9 лет, x1.5). По умолч.: 0 (любой).
Структура объекта order_schedule
ПараметрТипОписание
start_datetimeStringОтложенный запуск. Дата и время в формате "YYYY-MM-DD HH:MM:SS".
start_timeStringВремя начала показов ежедневно. Формат "HH:MM".
end_timeStringВремя окончания показов ежедневно. Формат "HH:MM".
excluded_daysArray[Integer]Массив дней недели для исключения, где 1=Понедельник, ..., 7=Воскресенье.

Action: update — Обновление заказа

Изменяет параметры существующего заказа.

ПараметрТипОбязательныйОписание
actionStringДаВсегда "update".
order_idIntegerДаID заказа, который нужно изменить.
is_onIntegerНетЗапустить (1) или остановить (0) кампанию.
in_archiveIntegerНетПереместить в архив (1) или вернуть из архива (0).
Все остальные параметры из действия create также не обязательны. Передавайте только те, которые хотите изменить. Ссылку link нельзя изменить, если по заказу уже есть подписчики.

Action: info — Получение информации

Возвращает полную информацию о заказе, включая все настройки таргетинга и итоговые цены с коэффициентами.

ПараметрТипОбязательныйОписание
actionStringДаВсегда "info".
order_idIntegerДаID заказа для получения информации.

Примеры и Ответы

Пример запроса на создание (create)
{
                      "action": "create",
                      "link": "https://t.me/durov",
                      "ads_type": "channel",
                      "name": "Тестовый заказ через API",
                      "quantity_all": 1000,
                      "price": 2.5,
                      "user_parameters": {
                        "gender": "male",
                        "languages": [1, 3],
                        "has_photo": 1
                      },
                      "order_schedule": {
                        "start_time": "09:00",
                        "end_time": "22:00",
                        "excluded_days": [6, 7]
                      }
                    }
Пример ответа на создание (create)
{
                      "status": "ok",
                      "code": 200,
                      "message": "Заказ успешно создан и отправлен на модерацию",
                      "response": {
                        "order_id": 12345
                      }
                    }
Пример ответа на получение информации (info)

Ответ включает все параметры заказа, текущий прогресс, а также итоговые цены и примененные коэффициенты.

ПолеТипОписание
order_idIntegerID заказа.
statusString Текущий статус заказа:
  • Moderation — На модерации.
  • Processing — Активен и выполняется.
  • Stopped — Остановлен вручную.
  • Finished — Заказ выполнен (достигнут лимит quantity_all).
  • Rejected — Отклонен модератором.
  • Archived — Перемещен в архив (удален).
reasonStringПричина отклонения (только для статуса Rejected).
link, name, ......Все остальные параметры, переданные при создании/обновлении.
quantity_nowIntegerТекущее количество выполненных подписок.
remainsIntegerОставшееся количество подписок.
old_priceFloatБазовая цена за подписчика, указанная вами (до применения коэффициентов).
real_priceFloatИтоговая реальная цена за подписчика после применения всех коэффициентов.
user_parametersObjectОбъект с примененными настройками таргетинга.
order_scheduleObjectОбъект с примененными настройками расписания.
coefficientsObjectОбъект, в котором перечислены все примененные коэффициенты и их значения. Содержит ключ total с итоговым множителем.
{
                        "status": "ok",
                        "code": 200,
                        "message": "Информация о заказе получена",
                        "response": {
                            "order_id": 12345,
                            "status": "Processing",
                            "reason": null,
                            "link": "https://t.me/durov",
                            "name": "Тестовый заказ через API",
                            "ads_type": "channel",
                            "track_unsubscriptions": true,
                            "to_bot_member": null,
                            "quantity_all": 1000,
                            "quantity_day": 1000,
                            "quantity_now": 150,
                            "remains": 850,
                            "is_on": 1,
                            "in_archive": 0,
                            "old_price": 2.5,
                            "real_price": 4.13,
                            "is_lite": 0,
                            "sub_speed": null,
                            "user_parameters": {
                                "languages": [1, 3],
                                "ages": [5, 6],
                                "countries": [],
                                "cities": [101],
                                "devicestype": [],
                                "devicesos": [],
                                "gender": "male",
                                "only_premium": 0,
                                "has_photo": 1,
                                "has_username": 0,
                                "has_bio": 0,
                                "has_first_name": 0,
                                "has_ru_name": 0,
                                "has_fake_check": 0,
                                "old_account": 0
                            },
                            "order_schedule": {
                                "start_datetime": null,
                                "start_time": "09:00:00",
                                "end_time": "22:00:00",
                                "excluded_days": [6, 7]
                            },
                            "coefficients": {
                                "cities": 2.5,
                                "ages": 1.88,
                                "has_photo": 1.1,
                                "total": 3.48
                            }
                        }
                    }

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

HTTP КодКод ошибки (error_code)Описание
400price_too_lowУказанная price или price_premium ниже минимально допустимой.
400invalid_parameterПередан неверный параметр, например, track_unsubscriptions для заказа типа bot.
403forbidden_or_not_foundЗаказ не найден или вы не являетесь его владельцем (при update или info).
409update_forbiddenПопытка изменить поле, которое нельзя менять (например, ссылку в заказе, где уже есть подписчики).
409rejected, exist, moderationКонфликт при создании: ссылка уже отклонена, существует или на модерации.
422-Ошибка валидации данных. В поле message будет подробное описание.

Общие методы API

Эндпоинты, доступные разным типам пользователей.

Получение баланса

POST /get-balance

Возвращает текущий баланс аккаунта и краткую сводку по всем подключенным ботам. Аутентификация по токену пользователя.

Этот метод не требует параметров в теле запроса.

Пример успешного ответа (HTTP 200)

{
                      "status": "ok",
                      "code": 200,
                      "message": "Баланс и информация о ботах успешно получена",
                      "balance": 219.21,
                      "bots_info": [
                        {
                          "bot_id": 12345678,
                          "bot_username": "my_super_bot",
                          "total_followers": 60667,
                          "revenue": 120542.45
                        }
                      ]
                    }

Получение фильтров

GET /filters

Возвращает полный список всех доступных значений для настройки таргетинга в заказах (Advertiser API) и для исключения тематик в настройках ботов (Publisher API).

Примечание: Этот эндпоинт является публичным и не требует аутентификации.

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

Этот метод не требует параметров в теле запроса или в URL.

Пример запроса (cURL)

curl -X GET https://api.subgram.org/filters

Пример ответа (HTTP 200)

{
                      "filters": {
                        "ads": {
                          "countries": [
                            { "id": 1, "name": "Россия", "percentage": 75.4 },
                            { "id": 2, "name": "Украина", "percentage": 8.1 }
                          ],
                          "languages": [
                            { "id": 1, "name": "Русский", "percentage": 89.2 },
                            { "id": 2, "name": "Украинский", "percentage": 5.5 }
                          ],
                          "cities": [
                            { "id": 101, "name": "Москва", "percentage": 25.0 },
                            { "id": 102, "name": "Санкт-Петербург", "percentage": 12.3 }
                          ],
                          "ages": [
                            { "id": 5, "name": "18 - 24", "percentage": 40.5 },
                            { "id": 6, "name": "25 и старше", "percentage": 30.1 }
                          ],
                          "devicestype": [
                            { "id": "mobile", "name": "Смартфон/Планшет", "percentage": 92.3 },
                            { "id": "desktop", "name": "Компьютер/Ноутбук", "percentage": 7.7 }
                          ],
                          "devicesos": [
                            { "id": 1, "name": "Android", "percentage": 65.4 },
                            { "id": 2, "name": "iOS", "percentage": 25.1 }
                          ],
                          "forbidden_themes": [
                            { "id": "stars", "name": "Старсы/NFT", "percentage": 15.8 },
                            { "id": "adult", "name": "18+", "percentage": 10.2 }
                          ]
                        },
                        "bots": {
                          "forbidden_themes": [
                            { "id": "news", "name": "Новости", "percentage": 5.3 },
                            { "id": "useful", "name": "Полезное", "percentage": 12.1 }
                          ]
                        }
                      }
                    }

Описание полей ответа

ПолеТипОписание
filtersObjectКорневой объект, содержащий все фильтры.
filters.adsObjectОбъект с фильтрами, применимыми к заказам (Advertiser API).
filters.ads.*Array[Object]Массивы объектов для каждого типа фильтра (countries, languages, ages и т.д.).
filters.botsObjectОбъект с фильтрами, применимыми к настройкам ботов (Publisher API).
filters.bots.forbidden_themesArray[Object]Массив тематик рекламы, которые можно запретить к показу в ваших ботах.
Структура объекта в массивах
idInteger/StringУникальный идентификатор значения (например, ID страны или код категории), который нужно использовать при создании/обновлении заказов и ботов.
nameStringЧеловекочитаемое название значения (например, "Россия" или "Криптовалюта").
percentageFloatПримерная доля этого параметра среди всех пользователей в системе SubGram. Помогает оценить, насколько сильно фильтр сузит аудиторию.

Получение статистики

GET /statistic

Универсальный эндпоинт для получения статистики как по доходам (для владельцев ботов), так и по расходам (для рекламодателей). Аутентификация происходит по токену пользователя, переданному в query-параметре api_token.

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

ПараметрТипОбязательныйОписание
api_tokenStringДаВаш основной токен пользователя.
actionStringДа Тип запрашиваемой статистики:
  • Для рекламодателей: allads, ads, source.
  • Для владельцев ботов: allbots, bots, sponsor.
ads_idIntegerНетID заказа (обязателен для action=ads и action=source).
bot_idIntegerНетID бота (обязателен для action=bots и action=sponsor для одного бота).
start_dateStringНетНачальная дата (YYYY-MM-DD). По умолч.: 9 дней назад.
end_dateStringНетКонечная дата (YYYY-MM-DD). По умолч.: сегодня.
output_formatStringНетФормат ответа: html (для веб-страницы) или json (для API).

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

https://api.subgram.org/statistic?api_token=ВАШ_ТОКЕН&action=allads&output_format=json

Описание полей ответа (для output_format=json)

Ответ содержит объект data, структура которого зависит от параметра action.

ПолеТипОписание
Общие поля (для action=ads/allads/bots/allbots)
data.labelsArray[String]Массив меток (дат) для оси X на графике.
data.subscribers_dataArray[Integer]Массив данных о количестве подписок для каждой даты.
data.value_dataArray[Float]Массив данных о расходах/доходах для каждой даты.
data.avg_price_dataArray[Float]Массив данных о средней цене подписчика для каждой даты.
data.total_subscribersIntegerИтоговое количество подписок за период.
data.total_valueFloatИтоговые расходы/доходы за период.
Поля для action=source / sponsor
data.table_dataArray[Object]Массив объектов для построения таблицы. Структура зависит от action:
• Для source: bot_id, bot_nickname, subscribers, value, is_excluded.
• Для sponsor: link, ads_id, subscribers, is_excluded.
Дополнительные поля для action=bots/allbots
data.show_extended_tableBooleantrue, если у бота были подписки со "своих" заказов, что активирует расширенную таблицу.
data.table_dataArray[Object]Если show_extended_table равно true, объекты в массиве будут содержать детальную разбивку: service_subs, service_value, own_subs, own_value и т.д.
data.requests_statsObjectОбъект со статистикой по API-запросам к вашему боту. Включает total_requests, successful_requests и др.
Дополнительные поля для action=ads
data.language_statsArray[Object]Массив объектов со статистикой по языкам привлеченных пользователей. Пример: {"lang": "ru", "percentage": "85.4%"}.

Управление исключениями

POST /toggle-exclusion

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

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

Этот метод использует аутентификацию по токену пользователя, который необходимо передать как query-параметр.

POST /toggle-exclusion?api_token=ВАШ_ОСНОВНОЙ_API_ТОКЕН

Параметры запроса (Body, JSON)

ПараметрТипОбязательныйОписание
actionStringДаДействие, которое нужно выполнить:
  • exclude: Добавить в черный список.
  • activate: Убрать из черного списка.
contextStringДа Определяет логику работы. Ключевой параметр:
  • advertiser: Режим для рекламодателя. Вы исключаете бота из показа для своего заказа.
  • publisher: Режим для владельца бота. Вы исключаете спонсора из показа в ваших ботах.
ads_idIntegerДаID заказа. В зависимости от контекста это либо ваш заказ, который вы настраиваете, либо заказ спонсора, которого вы блокируете.
bot_idIntegerУсловно*ID бота. *Обязателен, если context равен advertiser. Для publisher он опционален (см. примеры).

Сценарии использования и примеры

Пример 1: Рекламодатель исключает бот из своей кампании

Вы (рекламодатель) хотите, чтобы ваш заказ с ID 12345 больше никогда не показывался в боте с ID 98765.

{
                      "action": "exclude",
                      "context": "advertiser",
                      "ads_id": 12345,
                      "bot_id": 98765
                    }
Пример 2: Владелец бота исключает спонсора (локально)

Вы (владелец бота) хотите, чтобы спонсор с ID заказа 54321 больше не показывался в конкретно вашем боте с ID 888777.

{
                      "action": "exclude",
                      "context": "publisher",
                      "ads_id": 54321,
                      "bot_id": 888777
                    }
Пример 3: Владелец бота исключает спонсора (глобально)

Вы (владелец бота) хотите, чтобы спонсор с ID заказа 54321 больше не показывался ни в одном из ваших ботов.

Для глобального исключения просто не передавайте параметр bot_id.
{
                      "action": "exclude",
                      "context": "publisher",
                      "ads_id": 54321
                    }

Ответы

Пример успешного ответа (HTTP 200)
{
                      "status": "ok",
                      "message": "Ресурс успешно исключен."
                    }

В случае успеха вы получите простое подтверждение. В случае ошибок (неверный токен, отсутствие прав на управление ресурсом) будет возвращен стандартный ответ с ошибкой и соответствующим HTTP-кодом (401, 403, 404).

Webhooks

Получайте уведомления о событиях подписки и отписки в реальном времени.

Что такое Вебхуки?

Вебхуки позволяют вашему серверу получать мгновенные POST-уведомления от SubGram о событиях, связанных с подписками пользователей. Это избавляет от необходимости постоянно опрашивать наш API методом /get-user-subscriptions для проверки статуса.

Как только пользователь подписывается или отписывается от ресурса по ссылке, выданной вашему боту, SubGram отправляет запрос на ваш URL с деталями этого события.

Шаг 1: Настройка

Настройка получения вебхуков производится в официальном боте SubGram: @subgram_officialbot.

  1. Откройте бота и перейдите в /start → Профиль.
  2. Нажмите кнопку "Вебхуки".
  3. Бот запросит у вас URL-адрес (endpoint) вашего сервера, куда SubGram будет отправлять POST-запросы. Укажите действительный и публично доступный URL (обязательно с https://).
  4. Далее бот предложит выбрать, какие типы событий вы хотите получать:
    • Только подписки (статусы subscribed, notgetted)
    • Только отписки (статус unsubscribed)
    • Подписки и отписки (все типы статусов)
Важно: Ваш сервер должен быть готов принимать POST-запросы на указанный URL и должен быть доступен из сети Интернет.

Шаг 2: Прием и обработка запроса

SubGram будет периодически (каждые несколько секунд) отправлять POST-запросы на ваш URL, если с момента последней отправки произошли новые события.

Аутентификация запроса

Для проверки того, что запрос действительно пришел от SubGram и относится к вашему боту, мы передаем API ключ вашего бота в HTTP-заголовке Api-Key. Вам следует всегда проверять этот заголовок на вашем сервере.

POST /your-webhook-endpoint HTTP/1.1
        "Content-Type": "application/json",
        "Api-Key": "ВАШ_API_КЛЮЧ_БОТА",
        ...

        { ... тело запроса ... }

Тело запроса (Payload)

Тело запроса содержит JSON-объект с ключом webhooks, значение которого — массив объектов. Каждый объект в массиве представляет одно событие.

{
          "webhooks": [
            {
              "webhook_id": 2757,
              "link": "https://t.me/+5hritdfa...",
              "user_id": 5440481282,
              "bot_id": 5811111111,
              "status": "subscribed",
              "subscribe_date": "2025-04-14"
            },
            {
              "webhook_id": 2763,
              "link": "https://t.me/+-gZ5RJI...",
              "user_id": 949444369,
              "bot_id": 5811111111,
              "status": "unsubscribed",
              "subscribe_date": "2025-04-14"
            }
          ]
        }

Шаг 3: Интерпретация данных

Каждый объект в массиве webhooks содержит следующие поля:

ПолеТипОписание
webhook_idIntegerУникальный ID события. Большее значение означает более позднее событие. Помогает определить порядок, если пользователь быстро подписался и отписался.
linkStringСсылка, через которую произошло взаимодействие.
user_idIntegerID пользователя Telegram, для которого пришло событие.
bot_idIntegerID вашего Telegram-бота, к которому относится это событие.
statusString Текущий статус подписки:
  • subscribed: Пользователь успешно подписался, и подписка засчитана.
  • notgetted: Пользователь подписан на ресурс, но подписка не засчитана по этой ссылке (был подписан ранее).
  • unsubscribed: Пользователь отписался от ресурса.
subscribe_dateStringДата (YYYY-MM-DD), когда пользователь впервые подписался на ресурс. Эта дата не меняется при отписке или повторной подписке.

Шаг 4: Рекомендации по обработке

  • Отвечайте быстро: Ваш сервер должен как можно быстрее ответить на запрос вебхука HTTP-статусом 200 OK, чтобы подтвердить получение. Длительную обработку (например, обновление базы данных) лучше выполнять асинхронно после отправки ответа.
  • Проверяйте Api-Key: Всегда проверяйте значение заголовка Api-Key, чтобы убедиться в подлинности запроса и понять, от какого из ваших ботов пришло событие.
  • Обрабатывайте массив: Помните, что в одном запросе может прийти массив из нескольких событий в поле webhooks. Ваш обработчик должен пройтись по всем элементам массива в цикле.
  • Используйте webhook_id: Если для вас важен точный порядок событий (подписка/отписка), используйте webhook_id для их сортировки или для предотвращения обработки устаревших событий, которые могли прийти с задержкой.
  • Логирование: Рекомендуется логировать все входящие вебхуки для отладки и анализа в случае возникновения проблем.