Plugin API v1
Справочник для разработчиков плагина ExTrack. Плагин живёт на игровом сервере и общается с панелью по HTTPS. Для Java есть готовый клиент: sdk/java. Он закрывает всё, что описано ниже: пачки логов, gzip, повторы при обрывах связи, очередь команд и защиту входа персонала.
- Базовый адрес:
https://<ваш домен>/api/plugin/v1 - Формат: JSON, UTF-8
- Авторизация: заголовок
Authorization: Bearer et_.... Токен выдаётся при создании сервера и пересоздаётся в «Настройки → Подключение». Старый токен перестаёт работать сразу. - Время: Unix-время в миллисекундах (
time), ответы сервера в ISO 8601 UTC. - Сжатие: тело можно отправлять с
Content-Encoding: gzip, лимит после распаковки 8 МБ.
Ошибки
{
"error": {
"code": "server_locked",
"message": "Сервер заморожен: он не входит в лимит тарифа владельца"
}
}
| HTTP | code | Что делать |
|---|---|---|
| 400 | validation_failed, bad_request |
Ошибка в данных, повтор не поможет |
| 401 | bad_token |
Токен неверный или пересоздан. Остановить отправку, написать в консоль |
| 403 | ip_not_allowed |
IP сервера не в белом списке (настраивается в панели) |
| 423 | server_locked |
Сервер заморожен по тарифу. Повторять раз в 5 минут |
| 429 | rate_limited |
Слишком часто. Подождать и повторить |
| 5xx | internal |
Повторить с экспоненциальной задержкой |
Лимиты на сервер: события 600 запросов в минуту, heartbeat 20, остальное от 60 до 300.
Жизненный цикл
- При запуске
POST /handshake. Ответ содержит настройки, список персонала и состояние защиты входа. - Каждые
settings.heartbeatSecondsсекундPOST /heartbeat. - События копятся в памяти и уходят пачками раз в
settings.flushSecondsсекунд или при набореsettings.maxBatch. - Отдельный поток держит long-poll
GET /actions?wait=25и выполняет команды из панели, затемPOST /actions/ack. - При выключении сервера отправить остаток буфера.
Если сервер не присылал heartbeat 3 минуты, панель считает его выключенным: закрывает сессии игроков и шлёт уведомление владельцу.
POST /handshake
{
"pluginVersion": "1.0.0",
"platform": "paper",
"mcVersion": "1.21.4",
"onlineMode": false,
"maxPlayers": 200,
"plugins": [
{ "name": "Essentials", "version": "2.21.0", "main": "com.earth2me.essentials.Essentials" },
{
"name": "LuckPerms",
"version": "5.4.140",
"main": "me.lucko.luckperms.bukkit.loader.BukkitLoaderPlugin"
}
]
}
platform: paper, spigot, folia, purpur, velocity, bungeecord, fabric, forge.
plugins необязателен: по пакету главного класса панель определяет, какой плагин вызвал ошибку в консоли.
Ответ:
{
"apiVersion": 1,
"server": { "id": "uuid", "name": "Demo SMP", "timezone": "Europe/Moscow" },
"locked": false,
"protection": { "enabled": true, "maxAttempts": 5, "timeoutSeconds": 300 },
"staff": [
{
"uuid": "...",
"name": "LenaMoon",
"nickname": "Lena_mod",
"role": "Модератор",
"isOwner": false
}
],
"settings": {
"heartbeatSeconds": 30,
"flushSeconds": 5,
"maxBatch": 500,
"actionsWaitSeconds": 25
}
}
Повторять при получении действия sync.
POST /heartbeat
{
"online": 57,
"maxPlayers": 200,
"tps": 19.94,
"mspt": 23.1,
"ramUsed": 6144,
"ramMax": 12288,
"chunks": 4200,
"entities": 9100,
"players": ["069a79f4-44e9-4726-a5be-fca90e38aaf5"]
}
ramUsed, ramMax в мегабайтах. players необязателен, но лучше передавать: по нему панель закрывает сессии
игроков, чей выход потерялся (краш, kill -9).
POST /events
{
"events": [
{
"type": "join",
"time": 1759340000000,
"player": { "uuid": "069a79f4-44e9-4726-a5be-fca90e38aaf5", "name": "Notch" },
"ip": "203.0.113.5",
"client": { "brand": "vanilla", "version": "1.21.4" },
"groups": ["default", "vip"]
},
{
"type": "block_break",
"player": { "uuid": "069a79f4-44e9-4726-a5be-fca90e38aaf5", "name": "Notch" },
"world": "world",
"x": 10,
"y": 64,
"z": -5,
"data": { "block": "minecraft:diamond_ore" }
}
]
}
До 2000 событий за запрос. Ответ: { "accepted": 2, "rejected": 0 }. Невалидные события отбрасываются по одному,
пачка целиком не отклоняется.
| Поле | Тип | |
|---|---|---|
type |
string | обязательно, см. таблицу ниже |
time |
number | мс, по умолчанию время приёма. Допустимо до 7 дней в прошлое |
player |
{uuid, name} |
обязательно для join и quit |
target |
{uuid?, name} |
второй участник: жертва, получатель ЛС, партнёр по обмену |
world, x, y, z |
string, int | координаты, по ним работает поиск «кто ломал здесь» |
message |
string | до 2000 символов |
data |
object | произвольные поля, до 4 КБ |
ip, client |
только для join: IP для поиска твинков, бренд и версия клиента |
|
groups |
string[] | только для join: группы прав игрока, например из LuckPerms |
Ники проверяются по шаблону [A-Za-z0-9_.*-]{1,32}, так что игроки Bedrock через Floodgate проходят.
Типы событий
| Категория | Типы |
|---|---|
| Входы | join, quit, world_change, teleport, gamemode |
| Чат | chat, command, private_message, sign_edit, book_edit, anvil_rename |
| Мир | block_break, block_place, explosion |
| Предметы | container_open, container_take, container_put, item_drop, item_pickup, craft |
| Бой | death, kill, mob_kill |
| Экономика | trade, economy, shop, donation |
| Модерация | punishment, anticheat, report, staff_auth |
| Прочее | advancement, custom |
donation - покупка за реальные деньги, из неё считаются выручка, ARPPU и популярные товары на вкладке
«Аналитика → Донатеры». Поле data строгое: { "amount": 349, "currency": "RUB", "product": "Premium на 30 дней" },
amount в целых единицах валюты, currency по умолчанию RUB. Событие без суммы или без игрока отклоняется.
Удобнее всего отправлять его из обработчика оплаты вашего донат-магазина.
IP из join используется для определения страны на вкладке «География», сам адрес видят только сотрудники с
доступом к IP.
private_message видят только сотрудники с правом «Личные сообщения и скрытые команды».
Аргументы команд авторизации (/login, /register, /changepassword, /l, /reg, /cp, /2fa, /email,
/extrack code и другие) маскируются сервером при приёме. Плагину тоже стоит маскировать их до отправки.
POST /tickets
Обращение из игры, например по /report <ник> <текст> или /bug <текст>.
{
"type": "complaint",
"message": "Грифер сломал дом",
"player": { "uuid": "...", "name": "Notch" },
"target": { "name": "Griefer" },
"location": { "world": "world", "x": 1240, "y": 70, "z": -320 }
}
Ответ { "number": 42 }. Не больше 3 обращений от одного игрока за 10 минут (429).
POST /punishments
Синхронизация наказаний, выданных в игре (LiteBans, AdvancedBan, Essentials).
{
"id": "litebans-ban-1234",
"type": "ban",
"player": { "uuid": "...", "name": "Griefer" },
"reason": "Гриферство",
"durationMinutes": 10080,
"issuer": { "uuid": "...", "name": "LenaMoon" },
"time": 1759340000000
}
type: warn, mute, kick, ban, ipban. durationMinutes: null значит навсегда. id из плагина наказаний
защищает от дублей. Если issuer.uuid привязан к сотруднику, наказание попадёт в его статистику.
POST /punishments/revoke
{ "id": "litebans-ban-1234", "by": "LenaMoon", "reason": "Апелляция одобрена" }
или по игроку: { "player": {...}, "type": "mute" }. Ответ { "revoked": 1 }.
POST /groups
Группы, изменившиеся между входами: купленный или истёкший донат, повышение. Группы при входе передаются в
событии join, этот запрос нужен только для изменений во время игры. С LuckPerms его удобно вызывать из
UserDataRecalculateEvent.
{
"players": [
{
"player": { "uuid": "069a79f4-44e9-4726-a5be-fca90e38aaf5", "name": "Notch" },
"groups": ["default", "premium"]
}
]
}
До 500 игроков за запрос. Передавайте полный список текущих групп: всё, чего в нём нет, считается снятым. Имена приводятся к нижнему регистру. Какие группы считать донатерскими, владелец выбирает в панели.
В Java SDK:
client.log(LogEvent.join(player, ip, brand, version, groups));
client.playerGroups(player, groups);
client.log(LogEvent.donation(player, 349, "RUB", "Premium на 30 дней"));
POST /console
Предупреждения и ошибки из консоли сервера для вкладки «Ошибки». Отправляйте строки уровня WARN и выше, до 500 за запрос, не чаще раза в 10 секунд. Повторы одной и той же ошибки панель сама объединяет, поэтому дедупликация на стороне плагина не нужна.
{
"records": [
{
"time": 1790000000000,
"level": "ERROR",
"logger": "Minecraft",
"thread": "Server thread",
"message": "Could not pass event PlayerInteractEvent to ShopGUIPlus v1.98.2",
"throwable": "java.lang.NullPointerException: ...\n\tat net.brcdev.shopgui.listener.ShopListener.onClick(ShopListener.java:88)"
}
]
}
| Поле | Описание |
|---|---|
level |
WARN, WARNING, ERROR, SEVERE или FATAL, остальные отбрасываются |
logger |
имя логгера, у плагинов совпадает с названием плагина |
message |
текст строки, цветовые коды удаляются на сервере |
throwable |
трассировка стека целиком, если есть |
Ответ: { "accepted": 1, "rejected": 0 }.
На Paper консоль пишется через Log4j2: достаточно добавить аппендер с фильтром WARN к корневому логгеру. Готовый пример есть в sdk/java/examples/PaperExample.java. Строки собственного логгера плагина ExTrack отправлять не нужно, иначе ошибка соединения будет порождать новые ошибки.
Защита входа персонала
Если владелец включил защиту, сотрудник после входа на сервер должен ввести код, который приходит ему на сайт
(и в Telegram, если привязан). До ввода кода плагин должен держать игрока «замороженным»: запрет движения,
чата, команд (кроме /extrack code), инвентаря и урона.
POST /staff/login
Вызывать на каждый вход игрока.
{ "player": { "uuid": "...", "name": "LenaMoon" }, "ip": "203.0.113.5" }
| Ответ | Действие |
|---|---|
{ "staff": false } |
обычный игрок |
{ "staff": true, "required": false } |
сотрудник, защита выключена или IP доверенный |
{ "staff": true, "required": true, "challengeId": "uuid", "expiresIn": 300, "maxAttempts": 5 } |
заморозить, ждать код |
Если API недоступен, а игрок есть в staff из последнего handshake и защита включена, безопаснее кикнуть
с просьбой зайти позже.
POST /staff/verify
{ "challengeId": "uuid", "code": "483920" }
| Ответ | Действие |
|---|---|
{ "ok": true } |
разморозить |
{ "ok": false, "attemptsLeft": 3 } |
неверный код |
{ "ok": false, "locked": true } |
попытки кончились, кикнуть. Владелец получит уведомление |
{ "ok": false, "reason": "expired" } |
время вышло или вход отклонён на сайте, кикнуть |
POST /staff/link
Привязка игрового аккаунта к сотруднику. Сотрудник получает код в карточке сервера на сайте и вводит в игре
/extrack link <код>.
{ "player": { "uuid": "...", "name": "LenaMoon" }, "code": "K7M2PX" }
Ответ { "ok": true, "nickname": "Lena_mod" } или { "ok": false, "reason": "invalid" | "taken" }.
После успешной привязки повторите handshake, чтобы обновить список персонала.
Очередь команд
GET /actions?wait=25
Long-poll: если команд нет, сервер держит запрос до wait секунд (максимум 30) и отвечает сразу, как только
команда появится. Таймаут клиента ставьте на 10-15 секунд больше wait.
{
"actions": [
{
"id": 17,
"type": "command",
"payload": { "command": "tempban Griefer 7d Гриферство", "punishmentId": "uuid" }
},
{
"id": 18,
"type": "message",
"payload": { "uuid": "...", "text": "[Обращение #42] Lena_mod: проверяем" }
},
{
"id": 19,
"type": "kick",
"payload": { "uuid": "...", "reason": "Вход отклонён владельцем аккаунта" }
},
{ "id": 20, "type": "sync", "payload": { "what": "config" } }
]
}
| type | Что сделать |
|---|---|
command |
выполнить от консоли, если команда есть в белом списке плагина (allowed-commands) |
message |
отправить сообщение игроку, если он онлайн |
kick |
кикнуть игрока |
sync |
повторить handshake |
Команды собираются из шаблонов наказаний, которые редактирует владелец. Белый список на стороне плагина обязателен: он защищает сервер, даже если аккаунт в панели скомпрометирован.
Неподтверждённая команда выдаётся повторно через 60 секунд, максимум 5 раз, затем помечается как просроченная.
POST /actions/ack
{
"results": [
{ "id": 17, "ok": true },
{ "id": 18, "ok": false, "message": "Игрок не в сети" }
]
}
Для команд наказаний результат виден в карточке игрока: «применено» или текст ошибки.
GET /players/{uuid}
Сводка для игровых команд вроде /extrack info <ник>:
{
"player": {
"id": 531,
"name": "Notch",
"firstSeenAt": "2026-09-01T10:00:00Z",
"playtimeSeconds": 86400,
"watched": false,
"activePunishments": [
{ "type": "mute", "reason": "Флуд", "expiresAt": "2026-10-02T10:00:00Z", "createdAt": "..." }
],
"notes": [
{ "body": "Подозрение на X-Ray", "pinned": true, "createdAt": "...", "author": "Lena_mod" }
],
"alts": 2
}
}
Проверка вручную
TOKEN=et_...
curl -s https://extrack.ru/api/plugin/v1/handshake \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d '{"pluginVersion":"dev","platform":"paper"}'