На этой странице
- Обзор
- Начало работы
- Автоматизация агентом
- Аутентификация
- Токены и цены
- API SMM-панели
- services
- add
- status
- balance
- cancel
- API платформы
- Каталог
- Аккаунт
- Двухфакторная защита и вход
- Настройки и оповещения
- Кампании
- Создание заказа
- Изменение заказа
- Логи и аналитика
- Профили имён и прокси
- Токены Discord
- Счета и платежи
- Сохранённая корзина
- Внутренние эндпоинты
- Формат ответов
- Объект сайта
- Объект кампании
- Состояние кампании
- Прогресс и возвраты
- Пример от начала до конца
- Вебхуки
- Настройка
- События
- Тело и заголовки
- Приём и повторы
- Журнал доставки
- Безопасность
- Ошибки
- Ограничения частоты
- Ограничения и примечания
Обзор
Toplistbot предоставляет два HTTP API. Оба работают с JSON поверх HTTPS, оба тратят один и тот же баланс токенов, и любого из них достаточно, чтобы вести кампании, ни разу не открыв панель.
API SMM-панели
Один эндпоинт, совместимый с Perfect Panel. Если ваша панель уже говорит на стандартном протоколе SMM, направьте её сюда — код менять не нужно.
API платформы
REST API, на котором работает панель: каталог, создание и управление кампаниями, логи доставки по каждому действию, профили, прокси и счета.
Базовый URL
https://backend.toplistbot.com/api
https://backend.toplistbot.comСправочник ниже написан для этих двух хостов. От выбора хоста зависит способ аутентификации — см. раздел «Аутентификация».
Начало работы
От нового аккаунта до работающей кампании за пять шагов. Всё ниже использует эндпоинт SMM — самый быстрый путь; API платформы работает так же, как только вы получите JWT.
Создайте аккаунт
Зарегистрируйтесь и подтвердите почту. Подтверждение начисляет 1 бесплатный токен — можно попробовать кампанию, прежде чем что-то тратить.
Скопируйте ключ API
Откройте личный кабинет и создайте ключ API. Относитесь к нему как к паролю — он тратит ваш баланс токенов. Ключ можно перевыпустить в любой момент, старый сразу перестаёт работать.
Найдите нужный сервис
Получите список всех сайтов, на которых можно оформить заказ. У каждой записи есть числовой id сервиса и тариф в токенах за 1 000 действий. Запишите id сайта, на котором хотите продвигаться.
cURLcurl -X POST https://backend.toplistbot.com/api/v2 -d "key=YOUR_API_KEY" -d "action=services"Оформите первый заказ
Отправьте id сервиса, URL, на котором будет работать кампания, и количество действий. Стоимость списывается сразу, а в ответе приходит id заказа.
cURLcurl -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"Отслеживайте выполнение
Запрашивайте статус по id заказа, чтобы видеть, сколько уже выполнено. Когда схема вас устроит, встройте те же вызовы в свою панель или скрипты.
cURLcurl -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-запросы. Держите ключ в переменной окружения, а не в самом сообщении.
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/getAll | JWT | Всё, где входит живой человек |
| /orders/getAll | API-ключ | Скрипты, планировщики, агенты |
Для автоматизации лучше пути без префикса. Нет входа, нет истечения, нет сессии, которую надо поддерживать: один ключ делает всё, и двухфакторная защита никогда не мешает.
API-ключ
Передавайте ключ полем `key` в любом запросе: параметром строки запроса, полем формы, полем JSON или заголовком `Authorization: Bearer`. Создавайте и меняйте его в панели. GET на /api/v2 возвращает статус ok — дешёвый способ проверить, что ключ жив.
# 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"curl https://backend.toplistbot.com/api/v2?key=YOUR_API_KEYJWT
Войдите, чтобы получить токен, и передавайте его как bearer-токен на маршрутах /api. Токены истекают, поэтому вызывайте /auth/refresh заранее. Если на аккаунте включена двухфакторная защита, при входе нужен ещё и `two_factor_code`.
curl -X POST https://backend.toplistbot.com/api/auth/login \
-H "Content-Type: application/json" \
-d '{"email":"[email protected]","password":"..."}'{
"access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...",
"token_type": "bearer",
"expires_in": 3600,
"user": { "id": 4211, "email": "[email protected]", "tokens": 528.41, "...": "..." }
}curl https://backend.toplistbot.com/api/orders/getAll \
-H "Authorization: Bearer YOUR_JWT"Вызовы из браузера
Все маршруты отвечают с открытым Access-Control-Allow-Origin, поэтому страница, расширение браузера или работающий в браузере агент могут обращаться к API напрямую — собственный прокси не нужен. Предупреждение выше остаётся в силе: ключ, отданный браузеру, — это опубликованный ключ, так что это для ваших собственных инструментов, а не для публичной страницы.
// 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_in_tokens = (rate * quantity) / 1000Сайт с тарифом 13 стоит 13 токенов за 1 000 действий, значит заказ на 500 обойдётся в 6,5 токена. Стоимость списывается при принятии заказа, а отмена возвращает неизрасходованный остаток.
Ответы balance и status указывают валюту USD ради совместимости с Perfect Panel, но значение — это баланс токенов, а не доллары. Считайте это число токенами.
API SMM-панели
Всё выполняет один эндпоинт. Передавайте поле `action` в каждом POST-запросе, чтобы выбрать операцию; каждый запрос также содержит ваш `key`.
https://backend.toplistbot.com/api/v2| Действие | Параметры |
|---|---|
services | — |
add | service, link, quantity, interval? |
status | orders |
balance | — |
cancel | orders |
`refill` и `refill_status` принимаются ради совместимости и обе отвечают «не реализовано». Здесь ничего нельзя дозаправить — оформите новый заказ.
action=services
Возвращает все сайты, на которых можно оформить заказ, с текущим тарифом и ограничениями. Используйте id `service` в вызовах add.
curl -X POST https://backend.toplistbot.com/api/v2 \
-d "key=YOUR_API_KEY" \
-d "action=services"[
{
"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обязательный | integer | Id сервиса из действия services. |
linkобязательный | url | URL, на котором работает кампания. Должен быть корректным URL. |
quantityобязательный | integer | Количество действий, от 1 до 50000. |
interval | integer | Действий в час. По умолчанию 15, максимум 4000, и не может превышать собственный лимит сайта. |
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"{
"order_id": 184223
}action=status
Возвращает прогресс по одному или нескольким заказам. Передайте один id, чтобы получить простой объект, или список через запятую.
curl -X POST https://backend.toplistbot.com/api/v2 \
-d "key=YOUR_API_KEY" \
-d "action=status" \
-d "orders=184223"{
"charge": 13.5,
"start_count": 0,
"status": "Completed",
"remains": 1000,
"currency": "USD"
}При нескольких id ответ индексируется по id заказа, а неизвестные или чужие заказы возвращают запись об ошибке, не обрушивая весь запрос.
{
"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 -X POST https://backend.toplistbot.com/api/v2 \
-d "key=YOUR_API_KEY" \
-d "action=balance"{
"balance": 528.41,
"currency": "USD"
}action=cancel
Останавливает заказ и возвращает неизрасходованный остаток на баланс. Завершённые заказы отменить нельзя.
curl -X POST https://backend.toplistbot.com/api/v2 \
-d "key=YOUR_API_KEY" \
-d "action=cancel" \
-d "orders=184223,184224"[
{ "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 "https://backend.toplistbot.com/orders/getAllWebsites"Просите меньше
Полный каталог занимает около 665 КБ, и половину этого веса дают два поля, с которыми вы никогда не оформите заказ: JSON-блок популярности и маркетинговое описание. Оставьте только поля, которыми заказываете, отбросьте неактивные сайты — и останется около 40 КБ.
# 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_hour | integer | Потолок доставки, обрезается до максимума самого сайта. |
extra_col | string | Свободное текстовое поле, которое хранится с заказом. |
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
}
]'200 OK
Successfully purchased with token balanceСтрока подписки
Для сайтов, у которых признак `subscribeable` равен 1. Цена считается как subscription_price_1d сайта, умноженная на число дней и на скидку уровня: Weekly — 0,90, Monthly — 0,80, всё остальное — 1,00. Доставляемое количество выводится на сервере из собственного subscription_speed сайта, поэтому ничем из запроса его не изменить.
[
{
"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], поэтому корзина с тремя плохими строками чинится за один заход, а не за три.
{
"errors": {
"items.2.amount": ["Enter 0 or more votes; a negative amount is not allowed."]
}
}POST /orders/update
`id` обязателен; отправляйте только те поля, которые меняете. Заказы-подписки изменить нельзя.
| Параметр | Тип | Описание |
|---|---|---|
idобязательный | integer | Кампания, которую меняем. |
amount_to_do | integer | Новое общее число действий. Увеличение списывает разницу, уменьшение возвращает её, между изменениями пауза в 30 секунд. |
url | url | Адрес кампании. |
custom_name | string | Ваше собственное название кампании. |
custom_max_per_hour | integer | Потолок доставки. |
username_profile_id | integer | Привязать профиль имён. |
proxy_profile_id | integer | Привязать профиль прокси. |
http_referral | url | Реферер, отправляемый с каждым действием. |
extra_col | string | Свободное текстовое поле. |
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 -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 у кампании. Приводите их к числу до арифметики, иначе получите склейку строк вместо сложения.
{
"accept_rate": "70", // string, not number
"timeout": "150000", // string, not number
"custom_max_per_hour": "60" // string, not number
}Объект сайта
Возвращают /orders/getAllWebsites и /orders/getWebsiteDetailsByName; он же вложен в каждую кампанию под ключом `website`.
| Параметр | Тип | Описание |
|---|---|---|
id | integer | Идентификатор сайта. Передаётся как `id` в строке корзины или как `service` в SMM-эндпоинте. |
name | string | Отображаемое имя и та самая строка, с которой сравнивает /orders/getWebsiteDetailsByName. |
price_per_1000 | number | Токенов за 1 000 принятых действий. Именно это число использует формула стоимости. |
accept_rate | string | Процент отправленных действий, которые принимаются. От него зависят все расчёты прогресса и возврата. |
max_per_hour | integer | Собственный потолок доставки сайта. И custom_max_per_hour, и interval в SMM обрезаются по нему. |
active | integer | 1 означает, что заказ возможен. Неактивные сайты тоже возвращаются, поэтому фильтруйте сами. |
vote_reset_time | integer | Через сколько часов сайт примет следующее действие от той же личности. |
speed_changeable | integer | 1 означает, что сайт учитывает заданную вами скорость доставки. |
referer_must_be_set | integer | 1 означает, что у кампании должен быть задан http_referral. |
optional_data_possible | integer | 1 означает, что сайт принимает поле optional_data кампании. |
track_votes | integer | 1 означает, что для кампаний на этом сайте доступны логи по каждому действию. |
subscribeable | integer | 1 означает, что строки подписки принимаются. |
subscription_price_1d | number | Токенов за день подписки, до скидки уровня. |
subscription_speed | integer | Действий в час, которые выдаёт подписка. Количество выводится из этого на сервере, а не из вашего запроса. |
Опущенные здесь поля обслуживают интерфейс самой панели. Читайте их, если хотите, но они не входят в контракт интеграции и могут измениться без предупреждения.
Объект кампании
Возвращают /orders/getAll и /orders/get/:id. Обратите внимание на то, чего нет: поля статуса не существует.
| Параметр | Тип | Описание |
|---|---|---|
id | integer | Идентификатор кампании. Все эндпоинты раздела «Кампании» принимают его как `id`. |
vote_website_id | integer | Сайт, на котором работает кампания. |
website | object | Полный объект сайта, вложенный внутрь. Есть в /orders/getAll и отсутствует в /orders/get/:id. |
url | string | Адрес кампании — тот самый `ownName`, который вы отправили при оформлении. |
custom_name | string | Ваше название кампании или null. |
amount_to_do | integer | Куплено принятых действий. Считает принятые действия, а не попытки. |
amount_done | integer | Отправлено действий на текущий момент. Единица измерения не та же, что у amount_to_do — см. раздел о прогрессе. |
running | integer | 1 — доставляет, 0 — на паузе. |
done | integer | 1 означает закрыта: отменена, возвращена, обе величины обнулены. Это не признак завершения. |
archive | integer | 1 означает в архиве. Архивные кампании всё равно возвращаются из /orders/getAll. |
custom_max_per_hour | string | Ваш потолок доставки для этой кампании, строкой. |
max_votes_per_day | integer | Дневной лимит действий или null, если лимита нет. |
is_subscription | integer | 1 означает подписку. Подписки нельзя редактировать после покупки. |
paused_unpaused | datetime | Когда кампанию в последний раз ставили на паузу, возобновляли или меняли объём. Именно отсюда отсчитывается 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 наполовину доставленная выглядит нетронутой. Всегда сначала переводите отправленные действия в принятые.
// 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 платформы вообще не нужен.
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-запрос на выбранный вами адрес. С его помощью можно награждать игроков в тот же момент, когда их голос засчитан, сверяться с собственными логами или сразу замечать, что кампания перестала доставлять голоса.
Один адрес на аккаунт
Вебхук привязан к вашему аккаунту, а не к отдельной кампании. Все ваши кампании отправляют события на один и тот же адрес, и в каждом событии указано, из какой кампании оно пришло.
Настройка вебхука
Четыре шага в панели — и ничего не нужно устанавливать.
Откройте карточку «Webhook»
В панели перейдите в раздел «Account» и откройте вкладку «Notifications».
Вставьте адрес
URL эндпоинта на вашем сервере, который будет принимать события. Он должен начинаться с https:// и быть доступен из интернета.
Выберите события
Прошедшие и непрошедшие голоса включены по умолчанию. Начало голоса (vote.started) выключено — включайте его, только если действительно на него реагируете.
Сохраните и отправьте тест
Адрес проверяется при сохранении, и вы сразу узнаете, если его не удаётся найти или он указывает на частную сеть. Затем нажмите «Send test»: тестовое событие уйдёт немедленно, а карточка покажет, что ответил ваш сервер.
Через API
Те же настройки доступны через API платформы с JWT. API-ключом их изменить нельзя: ключ нужен для работы с кампаниями и не должен позволять перенаправить события вашего аккаунта куда-то ещё.
| Параметр | Тип | Описание |
|---|---|---|
urlобязательный | url | Ваш HTTPS-эндпоинт, до 500 символов. Обязателен при каждом сохранении. |
enabled | boolean | Переключатель вкл/выкл. По умолчанию true. Должен быть настоящим булевым значением JSON. |
events | string[] | Какие события отправлять. По умолчанию — уже сохранённый набор, а для нового вебхука — vote.succeeded и vote.failed. |
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"]
}'{
"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 -X POST https://behind.toplistbot.com/api/user/webhooks/test \
-H "Authorization: Bearer YOUR_JWT"{
"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-телом.
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"
}| Параметр | Тип | Описание |
|---|---|---|
id | integer | Уникален для каждого события. Повторная отправка несёт тот же id, так что отсеивайте дубликаты по нему. |
event | string | Какое это событие. |
created_at | datetime | Когда событие было зафиксировано, в UTC. |
order_id | integer | Кампания, к которой относится голос. Тот же id, что используют эндпоинты кампаний. |
vote_job_id | string | Наш идентификатор этого голоса. Указывайте его, когда обращаетесь в поддержку по конкретному голосу. Может быть null. |
site | string | Адрес голосования кампании — в том виде, в каком вы его ввели. |
username | string | Имя пользователя, от которого отдан голос. Сопоставляйте события с собственными логами по нему, а не по 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.
Минимальный приёмник: сначала ответить, затем отсеять дубликаты, и только потом выполнять работу.
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 "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"{
"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Зависимость недоступна. Повторите позже.
{
"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` — чтобы узнать доставленное.
- Подписки нельзя менять после покупки — приостановите или отмените их.
- Сайты-каталоги устанавливают собственные правила и со временем их меняют. Вы сами отвечаете за то, чтобы использование сервиса соответствовало правилам любого сайта, на котором вы продвигаетесь. Мы не обещаем какую-либо позицию или место в рейтинге.
