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

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

Всё, что делает панель, доступно по HTTP. Направьте SMM-панель на один эндпоинт или управляйте всей платформой — каталогом, кампаниями, логами, счетами — из скрипта или ИИ-агента.

На этой странице

Обзор

Toplistbot предоставляет два HTTP API. Оба работают с JSON поверх HTTPS, оба тратят один и тот же баланс токенов, и любого из них достаточно, чтобы вести кампании, ни разу не открыв панель.

Базовый URL

Base URL
https://backend.toplistbot.com/api
https://backend.toplistbot.com

Справочник ниже написан для этих двух хостов. От выбора хоста зависит способ аутентификации — см. раздел «Аутентификация».

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

От нового аккаунта до работающей кампании за пять шагов. Всё ниже использует эндпоинт SMM — самый быстрый путь; API платформы работает так же, как только вы получите JWT.

  1. Создайте аккаунт

    Зарегистрируйтесь и подтвердите почту. Подтверждение начисляет 1 бесплатный токен — можно попробовать кампанию, прежде чем что-то тратить.

  2. Скопируйте ключ API

    Откройте личный кабинет и создайте ключ API. Относитесь к нему как к паролю — он тратит ваш баланс токенов. Ключ можно перевыпустить в любой момент, старый сразу перестаёт работать.

  3. Найдите нужный сервис

    Получите список всех сайтов, на которых можно оформить заказ. У каждой записи есть числовой id сервиса и тариф в токенах за 1 000 действий. Запишите id сайта, на котором хотите продвигаться.

    cURL
    curl -X POST https://backend.toplistbot.com/api/v2 -d "key=YOUR_API_KEY" -d "action=services"
  4. Оформите первый заказ

    Отправьте id сервиса, URL, на котором будет работать кампания, и количество действий. Стоимость списывается сразу, а в ответе приходит id заказа.

    cURL
    curl -X POST https://backend.toplistbot.com/api/v2 \
      -d "key=YOUR_API_KEY" \
      -d "action=add" \
      -d "service=9" \
      -d "link=https://arena-top100.com/index.php?a=in&u=yourserver" \
      -d "quantity=1000"
  5. Отслеживайте выполнение

    Запрашивайте статус по id заказа, чтобы видеть, сколько уже выполнено. Когда схема вас устроит, встройте те же вызовы в свою панель или скрипты.

    cURL
    curl -X POST https://backend.toplistbot.com/api/v2 -d "key=YOUR_API_KEY" -d "action=status" -d "orders=184223"

Подключение Perfect Panel

Если вы используете Perfect Panel или совместимое ПО для SMM-панелей, писать код не нужно — добавьте Toplistbot как провайдера с этими настройками и импортируйте список сервисов.

URL API
https://backend.toplistbot.com/api/v2
Ключ API
YOUR_API_KEY
HTTP-метод
POST

Начните с небольшого объёма на одном сайте, чтобы убедиться, что формат ссылки принимается, и только потом масштабируйтесь. Неверная ссылка тоже расходует токены.

Автоматизация с ИИ-агентом

У этой страницы есть текстовый двойник, написанный для машин. Дайте агенту эту ссылку и свой API-ключ — у него будет всё нужное: полный список эндпоинтов, форматы запросов и ответов, арифметика цен, коды ошибок и готовые примеры.

Машиночитаемый справочник

Один документ, без аутентификации и без JavaScript. Скачайте его, вставьте в промпт или передайте ссылку инструменту, который умеет ходить в интернет.

https://toplistbot.com/llms.txt

Стартовый промпт

Вставьте это в Claude или любого агента, умеющего делать HTTP-запросы. Держите ключ в переменной окружения, а не в самом сообщении.

Prompt
Read https://toplistbot.com/llms.txt — it is the complete Toplistbot API reference.

My API key is in the TOPLISTBOT_KEY environment variable. Using the API-key
surface (paths without the /api prefix):

  1. list the sites in the catalog that cost under 20 tokens per 1,000 actions
  2. tell me my token balance
  3. propose a campaign for <my campaign URL> that fits a budget of <N> tokens

Do not place the order until I confirm the cost.

API-ключ — правильный вид доступа для агента: он не истекает, на него не влияет двухфакторная защита, а его ротация из панели мгновенно отзывает доступ, если это понадобится.

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

Есть два вида доступа, и какой нужен — зависит от пути, а не от эндпоинта. Почти каждый эндпоинт смонтирован дважды.

Префикс /api определяет вид доступа

За обоими путями стоит один и тот же обработчик. Уберите префикс /api — и API платформы примет долгоживущий API-ключ; оставьте его — и эндпоинт ждёт JWT, полученный при входе.

ПутьДоступДля чего
/api/orders/getAllJWTВсё, где входит живой человек
/orders/getAllAPI-ключСкрипты, планировщики, агенты

Для автоматизации лучше пути без префикса. Нет входа, нет истечения, нет сессии, которую надо поддерживать: один ключ делает всё, и двухфакторная защита никогда не мешает.

API-ключ

Передавайте ключ полем `key` в любом запросе: параметром строки запроса, полем формы, полем JSON или заголовком `Authorization: Bearer`. Создавайте и меняйте его в панели. GET на /api/v2 возвращает статус ok — дешёвый способ проверить, что ключ жив.

cURL
# any of these three carry the key
curl "https://backend.toplistbot.com/orders/getAll?key=YOUR_API_KEY"
curl -X POST https://backend.toplistbot.com/orders/pause -d "key=YOUR_API_KEY" -d "id=184223"
curl https://backend.toplistbot.com/orders/getAll -H "Authorization: Bearer YOUR_API_KEY"
Health check
curl https://backend.toplistbot.com/api/v2?key=YOUR_API_KEY

JWT

Войдите, чтобы получить токен, и передавайте его как bearer-токен на маршрутах /api. Токены истекают, поэтому вызывайте /auth/refresh заранее. Если на аккаунте включена двухфакторная защита, при входе нужен ещё и `two_factor_code`.

Login
curl -X POST https://backend.toplistbot.com/api/auth/login \
  -H "Content-Type: application/json" \
  -d '{"email":"[email protected]","password":"..."}'
Response
{
  "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...",
  "token_type": "bearer",
  "expires_in": 3600,
  "user": { "id": 4211, "email": "[email protected]", "tokens": 528.41, "...": "..." }
}
Authenticated request
curl https://backend.toplistbot.com/api/orders/getAll \
  -H "Authorization: Bearer YOUR_JWT"

Вызовы из браузера

Все маршруты отвечают с открытым Access-Control-Allow-Origin, поэтому страница, расширение браузера или работающий в браузере агент могут обращаться к API напрямую — собственный прокси не нужен. Предупреждение выше остаётся в силе: ключ, отданный браузеру, — это опубликованный ключ, так что это для ваших собственных инструментов, а не для публичной страницы.

JavaScript
// Works from a page, an extension, or a browser-based agent.
const sites = await fetch(
  'https://backend.toplistbot.com/orders/getAllWebsites'
).then(r => r.json())

Ваш API-ключ тратит настоящий баланс токенов. Держите его на сервере: любой ключ, попавший в браузер или в репозиторий, считайте скомпрометированным и меняйте в панели.

Токены и цены

Кампании оплачиваются токенами, купленными заранее. У каждого сайта есть тариф — сколько токенов стоит 1 000 действий кампании на нём — он возвращается в поле `rate` действия services.

Cost formula
cost_in_tokens = (rate * quantity) / 1000

Сайт с тарифом 13 стоит 13 токенов за 1 000 действий, значит заказ на 500 обойдётся в 6,5 токена. Стоимость списывается при принятии заказа, а отмена возвращает неизрасходованный остаток.

Ответы balance и status указывают валюту USD ради совместимости с Perfect Panel, но значение — это баланс токенов, а не доллары. Считайте это число токенами.

API SMM-панели

Всё выполняет один эндпоинт. Передавайте поле `action` в каждом POST-запросе, чтобы выбрать операцию; каждый запрос также содержит ваш `key`.

POSThttps://backend.toplistbot.com/api/v2
ДействиеПараметры
services—
addservice, link, quantity, interval?
statusorders
balance—
cancelorders

`refill` и `refill_status` принимаются ради совместимости и обе отвечают «не реализовано». Здесь ничего нельзя дозаправить — оформите новый заказ.

action=services

Возвращает все сайты, на которых можно оформить заказ, с текущим тарифом и ограничениями. Используйте id `service` в вызовах add.

cURL
curl -X POST https://backend.toplistbot.com/api/v2 \
  -d "key=YOUR_API_KEY" \
  -d "action=services"
Response
[
  {
    "service": 9,
    "name": "arena-top100.com 1000 upvotes",
    "type": "Default",
    "category": "Votes",
    "rate": 15,
    "min": 1,
    "max": 50000,
    "refill": false,
    "cancel": true
  }
]

`rate` указан в токенах за 1 000 действий. Для всех сервисов `min` равен 1, `max` — 50000.

action=add

Создаёт кампанию и сразу списывает её стоимость с баланса.

ПараметрТипОписание
keyобязательныйstringВаш ключ API.
actionобязательныйstringДолжно быть `add`.
serviceобязательныйintegerId сервиса из действия services.
linkобязательныйurlURL, на котором работает кампания. Должен быть корректным URL.
quantityобязательныйintegerКоличество действий, от 1 до 50000.
intervalintegerДействий в час. По умолчанию 15, максимум 4000, и не может превышать собственный лимит сайта.
cURL
curl -X POST https://backend.toplistbot.com/api/v2 \
  -d "key=YOUR_API_KEY" \
  -d "action=add" \
  -d "service=9" \
  -d "link=https://arena-top100.com/index.php?a=in&u=yourserver" \
  -d "quantity=1000" \
  -d "interval=60"
Response
{
  "order_id": 184223
}

action=status

Возвращает прогресс по одному или нескольким заказам. Передайте один id, чтобы получить простой объект, или список через запятую.

cURL
curl -X POST https://backend.toplistbot.com/api/v2 \
  -d "key=YOUR_API_KEY" \
  -d "action=status" \
  -d "orders=184223"
Response — single order
{
  "charge": 13.5,
  "start_count": 0,
  "status": "Completed",
  "remains": 1000,
  "currency": "USD"
}

При нескольких id ответ индексируется по id заказа, а неизвестные или чужие заказы возвращают запись об ошибке, не обрушивая весь запрос.

Response — multiple orders
{
  "184223": { "charge": 13.5, "start_count": 0, "status": "Completed", "remains": 1000, "currency": "USD" },
  "184224": { "error": "Incorrect order ID" }
}

Читайте `remains`, а не `status`

`status` всегда равен строке "Completed". Поле существует потому, что его требует любой клиент Perfect Panel, а панели считают любое другое значение поводом для дозаправки, которой эта платформа не предлагает. Прогресс — в числах: `remains` — это принятые действия, которые ещё предстоит доставить, поэтому `remains == 0` означает, что заказ выполнен. `start_count` — сколько доставлено, `charge` — сколько это стоило. Оба считаются с учётом процента принятия сайта, то есть считают купленные действия, а не сырые попытки.

`status` и `cancel` принимают не более 100 идентификаторов заказов за вызов. Группируйте вместо перебора: один вызов со 100 идентификаторами намного дешевле для обеих сторон, чем 100 вызовов.

action=balance

Возвращает остаток вашего баланса токенов.

cURL
curl -X POST https://backend.toplistbot.com/api/v2 \
  -d "key=YOUR_API_KEY" \
  -d "action=balance"
Response
{
  "balance": 528.41,
  "currency": "USD"
}

action=cancel

Останавливает заказ и возвращает неизрасходованный остаток на баланс. Завершённые заказы отменить нельзя.

cURL
curl -X POST https://backend.toplistbot.com/api/v2 \
  -d "key=YOUR_API_KEY" \
  -d "action=cancel" \
  -d "orders=184223,184224"
Response
[
  { "order": "184223", "cancel": 1, "refund": 4.5 },
  { "order": "184224", "cancel": { "error": "Incorrect order ID" } }
]

API платформы

Тот же REST API, который использует панель. Пути ниже написаны в форме с API-ключом, без префикса /api. Добавьте /api и замените ключ на JWT, чтобы использовать сессионную форму; эндпоинты, помеченные JWT, существуют только под /api.

Каталог и поиск

Открытый доступ, ключ не нужен. Начинать стоит с getAllWebsites: он отдаёт идентификатор, цену, часовой потолок и процент принятия — всё, что нужно, чтобы посчитать стоимость и задать параметры заказа.

  • GET/orders/getAllWebsitesОткрытыйПолный каталог: каждый сайт с тарифами, лимитами и метаданными
  • GET/orders/getAllBasicWebsitesDetailsОткрытый20 случайных названий сайтов — для виджетов и автодополнения
  • POST/orders/getWebsiteDetailsByNameОткрытыйОдин сайт по точному названию
  • POST/products/getSuggestionsОткрытыйСайты, связанные с набором идентификаторов
  • GET/products/demand?days=30ОткрытыйСколько заказов было на каждый сайт в последнее время
  • GET/products/tokensОткрытыйПакеты токенов, доступные к покупке
  • POST/products/suggestAPI-ключПопросить добавить новый сайт
  • GET/api/news/timelineОткрытыйИстория изменений продукта
cURL
curl "https://backend.toplistbot.com/orders/getAllWebsites"

Просите меньше

Полный каталог занимает около 665 КБ, и половину этого веса дают два поля, с которыми вы никогда не оформите заказ: JSON-блок популярности и маркетинговое описание. Оставьте только поля, которыми заказываете, отбросьте неактивные сайты — и останется около 40 КБ.

cURL + jq
# The whole catalog is ~665 KB across 396 sites.
# Projected to what you actually order with: ~40 KB.
curl -s "https://backend.toplistbot.com/orders/getAllWebsites" \
  | jq '[.[]
      | select(.active == 1)
      | {id, name, price_per_1000, max_per_hour, accept_rate, subscribeable}]'

Для ИИ-агента это разница между примерно 170 000 токенов и 10 000 — между тем, чтобы первый вызов сработал, и тем, чтобы он исчерпал окно контекста. Отфильтруйте до разбора.

Аккаунт и сессии

Регистрация требует браузера: её защищает проверка Cloudflare. Зарегистрируйтесь один раз на app.toplistbot.com, а всё дальнейшее автоматизируйте.

  • POST/api/auth/registerОткрытыйСоздать аккаунт — только из браузера, защищено капчей
  • POST/api/auth/loginОткрытыйОбменять учётные данные на JWT
  • POST/api/auth/refreshJWTВыдать новый JWT взамен истекающего
  • POST/api/auth/logoutJWTАннулировать текущий JWT
  • GET/api/auth/user-profileJWTТекущий аккаунт с балансом и API-ключом
  • GET/api/userJWTТот же объект пользователя по более короткому пути
  • GET/api/api_tokenAPI-ключОпределить владельца API-ключа — так проверяют ключ
  • POST/api/auth/reset-api-keyJWTСменить API-ключ; старый умирает сразу
  • POST/api/auth/fingerprintJWTЗаписать отпечаток браузера в аккаунт
  • POST/api/auth/ipJWTЗаписать текущий IP аккаунта
  • POST/api/auth/forgot-passwordОткрытыйОтправить ссылку на сброс пароля, действует 60 минут
  • POST/api/auth/reset-passwordОткрытыйЗадать новый пароль по присланному токену

Двухфакторная защита и вход

Двухфакторная защита прикрывает вход по паролю. На API-ключи она не распространяется — поэтому для работы без присмотра ключ подходит лучше.

  • POST/api/2fa/enableJWTНачать привязку: вернёт секрет, ссылку на QR и коды восстановления
  • POST/api/2fa/verifyJWTПодтвердить шестизначный код и включить двухфакторную защиту
  • POST/api/2fa/disableJWTВыключить двухфакторную защиту
  • POST/api/account/verification/requestJWTОтправить письмо с подтверждением на адрес аккаунта
  • GET/api/account/verification/confirm?token=ОткрытыйПоказать страницу подтверждения — ничего не записывает
  • POST/api/account/verification/confirmОткрытыйЗавершить подтверждение
  • GET/api/auth/googleОткрытыйНачать вход через Google
  • GET/api/auth/google/callbackОткрытыйВозврат после входа через Google
  • GET/api/auth/discordОткрытыйНачать вход через Discord
  • GET/api/auth/discord/callbackОткрытыйВозврат после входа через Discord

Настройки и оповещения

Переключатели писем и уведомлений, ваш вебхук и лента оповещений аккаунта.

  • GET/api/user/email-preferencesJWTСостояние подписки на маркетинговые письма
  • POST/api/user/email-preferencesJWTИзменить его
  • GET/api/user/notification-preferencesJWTНастройка всплывающих уведомлений о доставке
  • POST/api/user/notification-preferencesJWTИзменить её: нужен настоящий булев тип JSON
  • GET/api/user/alerts?limit=20JWTОповещения аккаунта, свежие первыми, с постраничностью по ?before
  • POST/api/user/alerts/readJWTПометить оповещение прочитанным
  • POST/api/user/alerts/dismissJWTСкрыть оповещение
  • GET/api/user/webhooksJWTВаш вебхук: адрес, события и состояние доставки
  • POST/api/user/webhooksJWTСохранить адрес, события и переключатель вкл/выкл
  • POST/api/user/webhooks/testJWTСразу отправить одно тестовое событие и сообщить, что ответил ваш сервер
  • GET/api/user/webhooks/deliveries?limit=25JWTЖурнал доставки вебхука, свежие первыми, с постраничностью по ?before
  • GET/api/email/unsubscribe?token=ОткрытыйОтписка в один клик по присланному токену

Кампании

Создавайте кампании, управляйте ими на ходу и завершайте их. Это ядро API платформы.

  • GET/orders/getAllAPI-ключВаши кампании, свежие первыми, вместе с их сайтами
  • GET/orders/get/{id}API-ключОдна кампания
  • POST/orders/checkoutAPI-ключСоздать кампании и списать с баланса
  • POST/orders/updateAPI-ключИзменить кампанию
  • POST/orders/pauseAPI-ключПриостановить активную кампанию
  • POST/orders/unpauseAPI-ключВозобновить приостановленную кампанию
  • POST/orders/archiveAPI-ключАрхивировать кампанию
  • POST/orders/unarchiveAPI-ключВернуть кампанию из архива
  • PATCH/orders/updateLimitAPI-ключЗадать или снять дневной потолок действий

POST /orders/checkout

Тело запроса — это JSON-массив строк корзины на верхнем уровне, а не объект. Каждая строка — отдельная кампания. Вся корзина проверяется до любого списания, а списание и вставки идут одной транзакцией: заказ либо проходит целиком, либо не проходит вовсе.

Строка с фиксированным количеством

Обычный случай: заданное число действий для одного адреса.

ПараметрТипОписание
idобязательныйintegerИдентификатор сайта из /orders/getAllWebsites.
amountобязательныйintegerСколько действий доставить. От 0 и не более 2 147 483 647.
ownNameобязательныйurlАдрес кампании. Сохраняется как поле `url` заказа.
custom_max_per_hourintegerПотолок доставки, обрезается до максимума самого сайта.
extra_colstringСвободное текстовое поле, которое хранится с заказом.
cURL
curl -X POST "https://backend.toplistbot.com/orders/checkout?key=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '[
    {
      "id": 9,
      "amount": 1000,
      "ownName": "https://arena-top100.com/index.php?a=in&u=yourserver",
      "custom_max_per_hour": 60
    }
  ]'
Response
200 OK
Successfully purchased with token balance

Строка подписки

Для сайтов, у которых признак `subscribeable` равен 1. Цена считается как subscription_price_1d сайта, умноженная на число дней и на скидку уровня: Weekly — 0,90, Monthly — 0,80, всё остальное — 1,00. Доставляемое количество выводится на сервере из собственного subscription_speed сайта, поэтому ничем из запроса его не изменить.

Body
[
  {
    "type": "subscription",
    "website": { "id": 9 },
    "subscription_days": 30,
    "tier": { "name": "Monthly" },
    "url": "https://arena-top100.com/index.php?a=in&u=yourserver"
  }
]

Ответы

  • 200Все строки созданы, с баланса списано. Тело ответа — обычный текст.
  • 400Тело запроса не является корректным JSON.
  • 402Не хватает токенов. В сообщении указана нужная сумма, списания не было.
  • 422Одна или несколько строк некорректны. Списания не было.
  • 429Та же корзина отправлялась в последние 60 секунд. Повторите после указанной паузы.

Ответ 422 указывает на проблемную строку: ошибки нумеруются как items.[index].[field], поэтому корзина с тремя плохими строками чинится за один заход, а не за три.

422 body
{
  "errors": {
    "items.2.amount": ["Enter 0 or more votes; a negative amount is not allowed."]
  }
}

POST /orders/update

`id` обязателен; отправляйте только те поля, которые меняете. Заказы-подписки изменить нельзя.

ПараметрТипОписание
idобязательныйintegerКампания, которую меняем.
amount_to_dointegerНовое общее число действий. Увеличение списывает разницу, уменьшение возвращает её, между изменениями пауза в 30 секунд.
urlurlАдрес кампании.
custom_namestringВаше собственное название кампании.
custom_max_per_hourintegerПотолок доставки.
username_profile_idintegerПривязать профиль имён.
proxy_profile_idintegerПривязать профиль прокси.
http_referralurlРеферер, отправляемый с каждым действием.
extra_colstringСвободное текстовое поле.
cURL
curl -X POST "https://backend.toplistbot.com/orders/update?key=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"id":184223,"amount_to_do":2000,"custom_name":"EU launch"}'

Дневной потолок

`type: "delete"` снимает потолок. Валидатор всё равно требует `max_votes_per_day` — отправьте любое целое число.

cURL
curl -X PATCH "https://backend.toplistbot.com/orders/updateLimit?key=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"id":184223,"max_votes_per_day":500,"type":"set"}'

Логи и аналитика

Данные о доставке — по каждому действию и в агрегате. Они читают отдельную базу логов и медленнее остального API: опрашивайте их раз в минуты, а не в секунды.

  • GET/orders/logs/{id}API-ключЛог доставки кампании по каждому действию
  • GET/orders/graph/{id}API-ключВременной ряд одной кампании, готовый к отрисовке
  • GET/api/orders/graph/summaryJWTОдин ряд по всем вашим кампаниям
  • GET/orders/grouped/usernames/{id}API-ключДоставки, сгруппированные по имени пользователя
  • POST/orders/averageAPI-ключСредняя доставка по нескольким кампаниям
  • GET/api/logs/{id}/filtered-graphJWTОтфильтрованный временной ряд

Профили имён и прокси

Профиль имён — это именованный список имён пользователей, которые использует кампания. Профиль прокси — именованный список разрешённых стран для используемых IP. Привяжите любой из них к кампании через username_profile_id или proxy_profile_id в /orders/update.

  • GET/advanced/profile/getAPI-ключВаши профили имён
  • GET/advanced/profile/get/{id}API-ключОдин профиль имён
  • POST/advanced/profile/createAPI-ключСоздать профиль имён или перезаписать по идентификатору
  • DELETE/advanced/profile/delete/{id}API-ключУдалить профиль имён
  • GET/advanced/profile/proxy/getAPI-ключВаши профили прокси
  • GET/advanced/profile/proxy/get/{id}API-ключОдин профиль прокси
  • POST/advanced/profile/proxy/createAPI-ключСоздать профиль прокси или перезаписать по идентификатору
  • DELETE/advanced/profile/proxy/delete/{id}API-ключУдалить профиль прокси

Токены Discord

Для топов, которые требуют входа через Discord. Повторное добавление того же токена отклоняется как дубликат.

  • GET/api/discord-tokensJWTВаши токены Discord
  • POST/api/discord-tokensJWTДобавить токен
  • GET/api/discord-tokens/statsJWTСтатистика использования ваших токенов
  • GET/api/discord-tokens/{id}JWTОдин токен
  • PATCH/api/discord-tokens/{id}JWTИзменить токен или его активность
  • DELETE/api/discord-tokens/{id}JWTУдалить токен
  • PUT/api/discord-tokens/{id}/toggleJWTПереключить токен между включённым и выключенным

Счета и платежи

Покупка токенов всегда заканчивается на размещённой у провайдера странице оплаты, поэтому пополнение нельзя сделать полностью автоматическим. Всё, что после пополнения, — можно.

  • GET/invoices/getAPI-ключИстория счетов
  • GET/api/subscriptions/subscriptionsJWTАктивные подписки
  • GET/products/tokensByUserAPI-ключПакеты токенов по ценам вашего аккаунта
  • POST/company/getAPI-ключВаш платёжный адрес
  • POST/company/createAPI-ключЗадать его: country, region, city, address, postalCode
  • GET/api/stripe/checkout?product_id=JWTСсылка Stripe Checkout для пакета токенов
  • GET/api/stripe/subscription?plan=JWTСсылка Stripe Checkout для тарифа
  • GET/api/stripe/portalJWTСсылка на платёжный портал Stripe
  • GET/api/stripe/documentsJWTСчета и квитанции Stripe
  • GET/coinpayments/checkoutAPI-ключСсылка на оплату криптовалютой

Сохранённая корзина

Корзина панели, которая хранится на сервере и переживает смену устройства. Для оформления заказов она не нужна: /orders/checkout принимает корзину прямо в запросе.

  • GET/api/cartJWTСохранённая корзина
  • PUT/api/cartJWTЗаменить её
  • POST/api/cartJWTЗаменить её — то же, что PUT
  • DELETE/api/cartJWTОчистить её

Внутренние эндпоинты

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

  • POST/api/stripe/webhookСобытия оплаты Stripe, проверяются по подписи
  • POST/api/jobs/tickЗапускает подошедшие задачи, доступ по общему секрету
  • POST/api/internal/vote-eventsСобытия голосов от сервиса голосования — доступ по общему секрету
  • POST/api/pow/challengeПроверка proof-of-work при регистрации
  • POST/api/logs/updateУчёт активности в письмах
  • POST/api/order/{email}Создаёт заказ на другом аккаунте — только для списка администраторов
  • GET/reset-password/{token}Старая серверная страница сброса пароля, оставлена ради уже отправленных ссылок
  • GET/ОткрытыйПроверка доступности

Формат ответов

Почти всё, что вы будете читать, приходит в двух объектах: сайт — из эндпоинтов каталога и кампания — из эндпоинтов заказов. В каждом около сорока колонок; в таблицах ниже те, которые действительно нужны интеграции.

Три поля приходят JSON-строками, хотя содержат числа: accept_rate и timeout у сайта, custom_max_per_hour у кампании. Приводите их к числу до арифметики, иначе получите склейку строк вместо сложения.

Types to watch
{
  "accept_rate": "70",          // string, not number
  "timeout": "150000",          // string, not number
  "custom_max_per_hour": "60"   // string, not number
}

Объект сайта

Возвращают /orders/getAllWebsites и /orders/getWebsiteDetailsByName; он же вложен в каждую кампанию под ключом `website`.

ПараметрТипОписание
idintegerИдентификатор сайта. Передаётся как `id` в строке корзины или как `service` в SMM-эндпоинте.
namestringОтображаемое имя и та самая строка, с которой сравнивает /orders/getWebsiteDetailsByName.
price_per_1000numberТокенов за 1 000 принятых действий. Именно это число использует формула стоимости.
accept_ratestringПроцент отправленных действий, которые принимаются. От него зависят все расчёты прогресса и возврата.
max_per_hourintegerСобственный потолок доставки сайта. И custom_max_per_hour, и interval в SMM обрезаются по нему.
activeinteger1 означает, что заказ возможен. Неактивные сайты тоже возвращаются, поэтому фильтруйте сами.
vote_reset_timeintegerЧерез сколько часов сайт примет следующее действие от той же личности.
speed_changeableinteger1 означает, что сайт учитывает заданную вами скорость доставки.
referer_must_be_setinteger1 означает, что у кампании должен быть задан http_referral.
optional_data_possibleinteger1 означает, что сайт принимает поле optional_data кампании.
track_votesinteger1 означает, что для кампаний на этом сайте доступны логи по каждому действию.
subscribeableinteger1 означает, что строки подписки принимаются.
subscription_price_1dnumberТокенов за день подписки, до скидки уровня.
subscription_speedintegerДействий в час, которые выдаёт подписка. Количество выводится из этого на сервере, а не из вашего запроса.

Опущенные здесь поля обслуживают интерфейс самой панели. Читайте их, если хотите, но они не входят в контракт интеграции и могут измениться без предупреждения.

Объект кампании

Возвращают /orders/getAll и /orders/get/:id. Обратите внимание на то, чего нет: поля статуса не существует.

ПараметрТипОписание
idintegerИдентификатор кампании. Все эндпоинты раздела «Кампании» принимают его как `id`.
vote_website_idintegerСайт, на котором работает кампания.
websiteobjectПолный объект сайта, вложенный внутрь. Есть в /orders/getAll и отсутствует в /orders/get/:id.
urlstringАдрес кампании — тот самый `ownName`, который вы отправили при оформлении.
custom_namestringВаше название кампании или null.
amount_to_dointegerКуплено принятых действий. Считает принятые действия, а не попытки.
amount_doneintegerОтправлено действий на текущий момент. Единица измерения не та же, что у amount_to_do — см. раздел о прогрессе.
runninginteger1 — доставляет, 0 — на паузе.
doneinteger1 означает закрыта: отменена, возвращена, обе величины обнулены. Это не признак завершения.
archiveinteger1 означает в архиве. Архивные кампании всё равно возвращаются из /orders/getAll.
custom_max_per_hourstringВаш потолок доставки для этой кампании, строкой.
max_votes_per_dayintegerДневной лимит действий или null, если лимита нет.
is_subscriptioninteger1 означает подписку. Подписки нельзя редактировать после покупки.
paused_unpauseddatetimeКогда кампанию в последний раз ставили на паузу, возобновляли или меняли объём. Именно отсюда отсчитывается 30-секундная пауза между правками.

Опущенные здесь поля обслуживают интерфейс самой панели. Читайте их, если хотите, но они не входят в контракт интеграции и могут измениться без предупреждения.

Работает ли она? Закончилась ли?

У кампании нет поля статуса, поэтому состояние выводится из четырёх колонок. Проверяйте условия по порядку и берите первое совпадение.

ПроверкаОзначает
1done === 1Отменена. Неизрасходованный остаток возвращён, обе величины обнулены, кампания отправлена в архив.
2running === 0Вы поставили её на паузу. Возобновите через /orders/unpause.
3remaining_accepted === 0Всё оплаченное доставлено.
4running === 1Работает штатно.
5archive === 1Скрыта в панели, но всё ещё возвращается из /orders/getAll. Отфильтруйте её, если хотите совпасть с тем, что показывает панель.

Порядок важен. Отмена выставляет done и archive одновременно, поэтому проверка archive первой показала бы отменённую кампанию просто архивной, а проверка остатка раньше running показала бы приостановленную кампанию как доставляющую.

Прогресс и возвраты

amount_to_do считает принятые действия, amount_done — отправленные. Принимается лишь accept_rate процентов отправленных, поэтому величины в разных единицах и вычитать одну из другой напрямую неверно.

Это самая частая ошибка интеграции, и она проваливается молча: числа выглядят правдоподобно, а полоса прогресса просто врёт. При проценте принятия 70 завершённая кампания читается как выполненная на 70 процентов; при 50 наполовину доставленная выглядит нетронутой. Всегда сначала переводите отправленные действия в принятые.

JavaScript
// accept_rate arrives as a STRING, and it is per-site, not global.
const rate = Number(order.website.accept_rate)

// amount_done counts SUBMISSIONS; amount_to_do counts ACCEPTED actions.
// Convert before comparing them.
const delivered = order.amount_done * rate / 100
const remaining = Math.max(order.amount_to_do - delivered, 0)
const percent   = 100 * delivered / order.amount_to_do

// What cancelling right now would put back on your balance:
const refund    = (remaining / 1000) * order.website.price_per_1000

Та же арифметика оценивает отмену: возвращается неизрасходованный остаток по прайсовой цене сайта, так что вы можете узнать, чего стоит остановка кампании, ещё до решения.

Кампания от начала до конца

Шесть вызовов, один API-ключ, без браузера и без входа. То же самое через SMM-эндпоинт — это три вызова (services, add, status), и API платформы вообще не нужен.

bash
KEY=YOUR_API_KEY
BASE=https://backend.toplistbot.com

# 1. What can I order, and what does it cost?
curl -s "$BASE/orders/getAllWebsites" \
  | jq '.[] | {id, name, price_per_1000, max_per_hour}'

# 2. What can I afford?
curl -s -X POST "$BASE/api/v2" -d "key=$KEY" -d "action=balance"

# 3. Launch it.  cost = price_per_1000 * amount / 1000
curl -s -X POST "$BASE/orders/checkout?key=$KEY" \
  -H "Content-Type: application/json" \
  -d '[{"id":9,"amount":1000,
        "ownName":"https://arena-top100.com/index.php?a=in&u=me",
        "custom_max_per_hour":60}]'

# 4. Find the campaign that was just created.
curl -s "$BASE/orders/getAll?key=$KEY" | jq '.[0] | {id, url, amount_to_do, amount_done}'

# 5. Watch it. Poll every few minutes.
curl -s "$BASE/orders/graph/184223?key=$KEY"

# 6. Slow it down, or stop it.
curl -s -X PATCH "$BASE/orders/updateLimit?key=$KEY" \
  -H "Content-Type: application/json" -d '{"id":184223,"max_votes_per_day":200,"type":"set"}'
curl -s -X POST "$BASE/orders/pause?key=$KEY" \
  -H "Content-Type: application/json" -d '{"id":184223}'

Вебхуки

Периодический опрос показывает, что изменилось с момента последнего запроса. Вебхук сообщает об этом сразу: как только голос по одной из ваших кампаний начался, прошёл или не прошёл, Toplistbot отправляет POST-запрос на выбранный вами адрес. С его помощью можно награждать игроков в тот же момент, когда их голос засчитан, сверяться с собственными логами или сразу замечать, что кампания перестала доставлять голоса.

Один адрес на аккаунт

Вебхук привязан к вашему аккаунту, а не к отдельной кампании. Все ваши кампании отправляют события на один и тот же адрес, и в каждом событии указано, из какой кампании оно пришло.

Настройка вебхука

Четыре шага в панели — и ничего не нужно устанавливать.

  1. Откройте карточку «Webhook»

    В панели перейдите в раздел «Account» и откройте вкладку «Notifications».

  2. Вставьте адрес

    URL эндпоинта на вашем сервере, который будет принимать события. Он должен начинаться с https:// и быть доступен из интернета.

  3. Выберите события

    Прошедшие и непрошедшие голоса включены по умолчанию. Начало голоса (vote.started) выключено — включайте его, только если действительно на него реагируете.

  4. Сохраните и отправьте тест

    Адрес проверяется при сохранении, и вы сразу узнаете, если его не удаётся найти или он указывает на частную сеть. Затем нажмите «Send test»: тестовое событие уйдёт немедленно, а карточка покажет, что ответил ваш сервер.

Через API

Те же настройки доступны через API платформы с JWT. API-ключом их изменить нельзя: ключ нужен для работы с кампаниями и не должен позволять перенаправить события вашего аккаунта куда-то ещё.

ПараметрТипОписание
urlобязательныйurlВаш HTTPS-эндпоинт, до 500 символов. Обязателен при каждом сохранении.
enabledbooleanПереключатель вкл/выкл. По умолчанию true. Должен быть настоящим булевым значением JSON.
eventsstring[]Какие события отправлять. По умолчанию — уже сохранённый набор, а для нового вебхука — vote.succeeded и vote.failed.
cURL
curl -X POST https://behind.toplistbot.com/api/user/webhooks \
  -H "Authorization: Bearer YOUR_JWT" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.com/hooks/toplistbot/3f9c1a7e5b2d",
    "enabled": true,
    "events": ["vote.succeeded", "vote.failed"]
  }'
Response
{
  "available": true,
  "webhook": {
    "url": "https://example.com/hooks/toplistbot/3f9c1a7e5b2d",
    "enabled": true,
    "events": ["vote.succeeded", "vote.failed"],
    "failing": false,
    "retry_at": null,
    "last_ok_at": null,
    "last_error": null,
    "last_error_at": null,
    "created_at": "2026-10-06T09:12:44.000000Z",
    "updated_at": "2026-10-06T09:12:44.000000Z"
  },
  "events": ["vote.started", "vote.succeeded", "vote.failed"],
  "default_events": ["vote.succeeded", "vote.failed"]
}

Тест отправляется ровно один раз и никогда не повторяется. Ответ сообщает, дошёл ли он и что ответил ваш сервер.

cURL
curl -X POST https://behind.toplistbot.com/api/user/webhooks/test \
  -H "Authorization: Bearer YOUR_JWT"
Response — the test did not arrive
{
  "delivered": false,
  "http_status": 500,
  "error": "Your server had an error (HTTP 500).",
  "delivery": { "id": 1874, "event": "webhook.test", "status": "failed", "...": "..." }
}

События

Каждый запрос несёт ровно одно событие.

СобытиеКогда отправляется
vote.startedНачался голос по одной из ваших кампаний. По умолчанию выключено: удваивает число запросов, а большинству интеграций важен только результат.
vote.succeededГолос прошёл.
vote.failedГолос не прошёл. Причина не указывается — кампания просто переходит к следующему голосу.
webhook.testОтправляется только кнопкой «Send test» — и всегда, какие бы события вы ни выбрали.

Тело запроса и заголовки

Каждое событие — это POST-запрос с небольшим JSON-телом.

Request
POST /hooks/toplistbot/3f9c1a7e5b2d HTTP/1.1
Content-Type: application/json
User-Agent: Toplistbot-Webhooks/1.0 (+https://toplistbot.com)
X-Toplistbot-Event: vote.succeeded
X-Toplistbot-Delivery: 1873

{
  "id": 1873,
  "event": "vote.succeeded",
  "created_at": "2026-10-06T09:12:44.123Z",
  "order_id": 184223,
  "vote_job_id": "7f3c2a9e-1b4d-4c8e-9a2f-5d6e7f8a9b0c",
  "site": "https://arena-top100.com/index.php?a=in&u=yourserver",
  "username": "PlayerOne"
}
ПараметрТипОписание
idintegerУникален для каждого события. Повторная отправка несёт тот же id, так что отсеивайте дубликаты по нему.
eventstringКакое это событие.
created_atdatetimeКогда событие было зафиксировано, в UTC.
order_idintegerКампания, к которой относится голос. Тот же id, что используют эндпоинты кампаний.
vote_job_idstringНаш идентификатор этого голоса. Указывайте его, когда обращаетесь в поддержку по конкретному голосу. Может быть null.
sitestringАдрес голосования кампании — в том виде, в каком вы его ввели.
usernamestringИмя пользователя, от которого отдан голос. Сопоставляйте события с собственными логами по нему, а не по IP-адресу. Может быть null.

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

ЗаголовокОписание
Content-TypeВсегда application/json.
X-Toplistbot-EventИмя события, совпадает с полем event в теле.
X-Toplistbot-DeliveryИдентификатор события, совпадает с полем id в теле.
User-AgentУказывает отправителя. Не полагайтесь на него в вопросах безопасности.

Приём событий и повторы

Ответьте любым статусом 2xx в течение 5 секунд. Медленную работу — запись в базу данных или выдачу награды игроку — выполняйте уже после ответа или в собственной очереди.

  • 2xxДоставлено. Больше ничего не происходит.
  • 3xxРедиректы не отслеживаются. Событие считается недоставленным и не повторяется, поэтому сохраняйте сразу конечный адрес.
  • 4xxОтказ. Событие помечается как недоставленное и не повторяется: повторная отправка получила бы тот же ответ. Исключения — 408, 425 и 429: их мы повторяем.
  • 5xxПовторяется — как и тайм-ауты, отклонённые соединения и недействительные сертификаты.

Когда ваш сервер даёт сбой, мы сразу перестаём слать ему запросы, а не стучимся в закрытую дверь. Новые события ставятся в очередь, и мы пробуем снова через 1 минуту, затем через 2, 4, 8 и так далее — с интервалом до часа. Повторы запускаются раз в 5 минут, поэтому ожидание округляется вверх до ближайшего запуска. Как только ваш сервер снова отвечает, очередь отправляется начиная с самых старых событий. Если нажать «Send test» после исправления сервера, очередь отправится сразу.

Событие, которое не удалось доставить за 24 часа, помечается как недоставленное. Выключение вебхука удаляет всё, что ещё стоит в очереди.

Дубликаты возможны, порядок не гарантирован

Доставка выполняется как минимум один раз. В редких случаях — например, при тайм-ауте, когда ваш сервер уже обработал событие, — одно и то же событие приходит дважды. Храните id обработанных событий и пропускайте уже встречавшиеся. События также могут приходить не по порядку, особенно после повтора: если порядок для вас важен, сортируйте по created_at.

Минимальный приёмник: сначала ответить, затем отсеять дубликаты, и только потом выполнять работу.

JavaScript
import express from 'express'

const app = express()
const seen = new Set() // use your database in production

app.post('/hooks/toplistbot/3f9c1a7e5b2d', express.json(), (req, res) => {
  // 1. Answer straight away. Anything slower than 5 seconds is a failure.
  res.sendStatus(204)

  // 2. Skip duplicates. A retry carries the same id.
  const event = req.body
  if (seen.has(event.id)) return
  seen.add(event.id)

  // 3. Now do the work.
  if (event.event === 'vote.succeeded') {
    rewardPlayer(event.username, event.order_id)
  }
})

app.listen(3000)

Журнал доставки

Каждое отправленное событие записывается в журнал вместе со статусом, которым ответил ваш сервер, а при неудаче — с причиной. В панели журнал отображается под карточкой «Webhook». Через API записи идут от новых к старым, по умолчанию 25 на страницу, максимум 100. Доставленные события хранятся 7 дней, недоставленные — 30.

cURL
curl "https://behind.toplistbot.com/api/user/webhooks/deliveries?limit=25" \
  -H "Authorization: Bearer YOUR_JWT"

# next page: pass the id of the last delivery you received
curl "https://behind.toplistbot.com/api/user/webhooks/deliveries?limit=25&before=1850" \
  -H "Authorization: Bearer YOUR_JWT"
Response
{
  "deliveries": [
    {
      "id": 1873,
      "event": "vote.succeeded",
      "order_id": 184223,
      "status": "delivered",
      "attempts": 1,
      "http_status": 204,
      "error": null,
      "created_at": "2026-10-06T09:12:44.000000Z",
      "delivered_at": "2026-10-06T09:12:44.000000Z",
      "payload": { "id": 1873, "event": "vote.succeeded", "...": "..." }
    }
  ],
  "has_more": true,
  "available": true
}

Безопасность

  • Только HTTPS. События никогда не отправляются по незашифрованному соединению.
  • Адрес должен быть публичным. Адреса в частных или локальных сетях отклоняются при сохранении и повторно при каждой отправке — в том числе домен, который позже направили на такой адрес.
  • Никаких логинов и паролей в адресе. Вместо этого используйте секретный путь.
  • Сохранять можно не чаще 20 раз в минуту, а «Send test» — не чаще 5 раз в минуту.

Запросы пока не подписываются

Любой, кто знает адрес вашего вебхука, может отправить на него запрос, похожий на наш. Пока подписи нет, добавьте в путь адреса длинный случайный токен и держите адрес в секрете. Прежде чем выдавать что-то ценное по событию, подтвердите его через API: эндпоинты кампаний показывают ту же доставку с нашей стороны.

Ошибки

Ошибки возвращаются с соответствующим HTTP-статусом. При ошибках валидации приходит объект `errors`, где ключи — имена полей.

  • 400Запрос не удалось прочитать: некорректный JSON или действие, неизвестное SMM-эндпоинту.
  • 401Доступ отсутствует, истёк или неверен.
  • 402Не хватает токенов. Списания не было.
  • 403Аутентификация прошла, но действие не разрешено.
  • 404Такой записи нет — включая ту, что принадлежит другому пользователю.
  • 409Конфликтует с текущим состоянием аккаунта, например повторное включение двухфакторной защиты.
  • 422Проверка не пройдена. В теле названо каждое поле.
  • 429Превышена частота. В сообщении указано, сколько ждать.
  • 500Наша ошибка. Списания не было.
  • 503Зависимость недоступна. Повторите позже.
Validation error
{
  "errors": {
    "quantity": ["The quantity must be at least 1."]
  }
}

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

Несколько эндпоинтов отвечают обычным текстом, а не JSON: среди них /orders/checkout, /orders/pause и /orders/updateLimit. Ориентируйтесь на код статуса, а не на форму тела.

Ограничения частоты

Ответ 429 всегда сообщает, сколько ждать. Соблюдайте паузу вместо слепых повторов.

  • Одна и та же корзина принимается не чаще раза в 60 секунд.
  • Общее число действий кампании можно менять раз в 30 секунд.
  • Попытки входа ограничены по адресу и по IP.
  • Сброс пароля: 3 на адрес и 10 на IP за 15 минут.
  • `status` и `cancel` принимают не более 100 идентификаторов заказов за вызов.
  • Опрашивайте прогресс раз в минуты, а не в секунды. Доставка измеряется в действиях в час.

Ограничения и примечания

  • Количество в заказе должно быть от 1 до 50000 действий.
  • Интервал по умолчанию — 15 в час, максимум 4000. Запрос выше собственного лимита сайта отклоняется с ошибкой 400, в которой указан лимит.
  • Действия `refill` и `refill_status` не реализованы — вместо этого создайте новый заказ.
  • Поле `status` всегда равно строке "Completed" и не показывает прогресс. Используйте `remains == 0`, чтобы понять, что заказ завершён, и `start_count` — чтобы узнать доставленное.
  • Подписки нельзя менять после покупки — приостановите или отмените их.
  • Сайты-каталоги устанавливают собственные правила и со временем их меняют. Вы сами отвечаете за то, чтобы использование сервиса соответствовало правилам любого сайта, на котором вы продвигаетесь. Мы не обещаем какую-либо позицию или место в рейтинге.
Начните бесплатно

Начните продвижение прямо сейчас!

Подтвердите электронную почту и получите 1 бесплатный токен, чтобы попробовать наш сервис. Без обязательств.

Без банковской карты
Отмена в любой момент
Поддержка по почте и в Discord