API
REST и WebSocket для ботов, аналитики и интеграций со сторонними сервисами.
1Быстрый старт
Первый авторизованный запрос к API LootX за пять минут.
API LootX даёт то же, что видит терминал: инструменты, позиции, ордера, историю и поток рыночных данных. Всё на одном домене https://api.lootx.trade, всё в JSON, всё по HTTPS.
Получите токен
Создайте ключ в кабинете
Кабинет → API → Создать ключ. Ключ показывается один раз — сохраните его сразу.
Проверьте права
Ключу выдаются права: read (данные), trade (ордера), account (баланс и позиции). Выдавайте только нужные.
Сделайте первый запрос
Ключ передаётся заголовком Authorization: Bearer <token>.
curl https://api.lootx.trade/v1/instruments?exchange=bybit \
-H "Authorization: Bearer $LOOTX_TOKEN"const res = await fetch("https://api.lootx.trade/v1/instruments?exchange=bybit", {
headers: { Authorization: `Bearer ${process.env.LOOTX_TOKEN}` },
});
if (!res.ok) throw new Error(`API ${res.status}: ${await res.text()}`);
const { items } = (await res.json()) as { items: Instrument[] };import os, requests
r = requests.get(
"https://api.lootx.trade/v1/instruments",
params={"exchange": "bybit"},
headers={"Authorization": f"Bearer {os.environ['LOOTX_TOKEN']}"},
timeout=10,
)
r.raise_for_status()
items = r.json()["items"]Ответ:
{
"items": [
{
"symbol": "BTCUSDT",
"exchange": "bybit",
"kind": "linear",
"tickSize": "0.1",
"minQty": "0.001",
"status": "trading"
}
],
"nextCursor": null
}Поток данных
const ws = new WebSocket("wss://api.lootx.trade/v1/stream");
ws.onopen = () => {
ws.send(JSON.stringify({ op: "auth", token: process.env.LOOTX_TOKEN }));
ws.send(JSON.stringify({ op: "subscribe", channels: ["trades:bybit:BTCUSDT"] }));
};
ws.onmessage = (e) => {
const msg = JSON.parse(e.data);
if (msg.channel?.startsWith("trades:")) console.log(msg.data.price, msg.data.qty);
};Дальше
https://docs.lootx.trade/en/api/quickstart
2Интеграция с сервисами
Как соединить LootX с TradingView, Telegram, таблицами и собственными сервисами: вебхуки, подпись запросов, идемпотентность и обработка ошибок.
Интеграция нужна в трёх случаях: сигнал приходит снаружи и должен превратиться в действие; событие происходит внутри и должно уехать наружу; данные нужно забрать по расписанию. LootX закрывает все три — входящими вебхуками, исходящими событиями и REST API.
Карта возможностей
TradingView, n8n, Make, собственный скрипт — сигнал превращается в ордер.
Исходящие событияСделки, стопы и алерты уходят в ваш сервис, Telegram или таблицу.
REST и WebSocketПрямой доступ к данным и ордерам из своего кода.
Входящие вебхуки
Секрет и персональный URL лежат в кабинете: откройте поиск по документации на ⌘/Ctrl+K или перейдите в Кабинет → Интеграции. Персональный URL выглядит так:
https://api.lootx.trade/v1/hooks/in/9f3c1a7e-0b2d-4c8a-9f01-6e2b7d4c5a10Он не секрет сам по себе — секретом является подпись. URL можно вставить в любой сервис, умеющий слать HTTP POST.
/v1/hooks/in/:hookIdПринимает сигнал и выполняет действие, описанное в теле. Отвечает 202 Accepted, когда сигнал принят к исполнению, и 200 OK, когда он был уже обработан ранее (повтор).
Заголовки
| Заголовок | Обязателен | Описание |
|---|---|---|
Content-Type |
да | application/json |
X-LootX-Signature |
да | sha256=<hex> — HMAC-SHA256 тела запроса |
X-LootX-Timestamp |
да | Unix-время в секундах; допуск ±300 с |
Тело
| Поле | Тип | Описание |
|---|---|---|
action |
string | order, close, cancel-all или notify |
exchange |
string | Код биржи: bybit, binance, okx |
symbol |
string | Инструмент, например BTCUSDT |
side |
string | buy или sell — для action: order |
qty |
string | Объём. Строка, не число: см. предупреждение ниже |
orderType |
string | market или limit, по умолчанию market |
price |
string | Цена для лимитной заявки |
stopLoss |
string | Абсолютная цена стопа |
takeProfit |
string | Абсолютная цена тейка |
requestId |
string | Идентификатор для идемпотентности, до 64 символов |
comment |
string | Попадёт в журнал сделок |
Подпись запроса
Подпись считается от точного тела запроса — той же последовательности байт, что уходит в сеть. Не пересобирайте JSON перед подписью.
import crypto from "node:crypto";
const secret = process.env.LOOTX_HOOK_SECRET!;
const body = JSON.stringify({
action: "order",
exchange: "bybit",
symbol: "BTCUSDT",
side: "buy",
qty: "0.01",
stopLoss: "61250",
requestId: crypto.randomUUID(),
});
const ts = Math.floor(Date.now() / 1000).toString();
const signature = crypto
.createHmac("sha256", secret)
.update(`${ts}.${body}`)
.digest("hex");
const res = await fetch(HOOK_URL, {
method: "POST",
headers: {
"content-type": "application/json",
"x-lootx-timestamp": ts,
"x-lootx-signature": `sha256=${signature}`,
},
body,
});import hmac, hashlib, json, os, time, uuid, requests
secret = os.environ["LOOTX_HOOK_SECRET"].encode()
body = json.dumps({
"action": "order",
"exchange": "bybit",
"symbol": "BTCUSDT",
"side": "buy",
"qty": "0.01",
"stopLoss": "61250",
"requestId": str(uuid.uuid4()),
}, separators=(",", ":")) # без пробелов: подписываем то, что отправляем
ts = str(int(time.time()))
sig = hmac.new(secret, f"{ts}.{body}".encode(), hashlib.sha256).hexdigest()
requests.post(HOOK_URL, data=body, timeout=10, headers={
"content-type": "application/json",
"x-lootx-timestamp": ts,
"x-lootx-signature": f"sha256={sig}",
})$secret = getenv('LOOTX_HOOK_SECRET');
$body = json_encode([
'action' => 'order',
'exchange' => 'bybit',
'symbol' => 'BTCUSDT',
'side' => 'buy',
'qty' => '0.01',
'requestId' => bin2hex(random_bytes(8)),
], JSON_UNESCAPED_SLASHES);
$ts = (string) time();
$sig = hash_hmac('sha256', "$ts.$body", $secret);
$ch = curl_init(HOOK_URL);
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $body,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
"X-LootX-Timestamp: $ts",
"X-LootX-Signature: sha256=$sig",
],
]);
curl_exec($ch);TradingView
TradingView не умеет подписывать запросы — он шлёт произвольный текст на URL. Поэтому для алертов TradingView в кабинете включается отдельный режим: Интеграции → TradingView → Создать канал. Канал выдаёт URL с одноразово-длинным идентификатором и требует, чтобы в теле присутствовал ваш channelToken.
Создайте канал
Кабинет → Интеграции → TradingView. Скопируйте URL и токен.
Опишите правила исполнения
Здесь же задаются ограничения: какие инструменты разрешены, максимальный объём, обязателен ли стоп, что делать при встречном сигнале. Эти правила сильнее содержимого алерта — сигнал не сможет превысить заданный лимит.
Вставьте сообщение в алерт
В окне алерта TradingView выберите Webhook URL и вставьте тело:
{
"channelToken": "chn_8d2f…",
"action": "order",
"exchange": "bybit",
"symbol": "{{ticker}}",
"side": "{{strategy.order.action}}",
"qty": "{{strategy.order.contracts}}",
"requestId": "{{timenow}}-{{ticker}}",
"comment": "{{strategy.order.comment}}"
}Проверьте на бумажном счёте
У канала есть переключатель Тестовый режим: сигналы проходят весь путь, ордера не отправляются, а результат виден в журнале.
Идемпотентность
requestId — единственное, что отделяет «повторили запрос, потому что не получили ответ» от «открыли вторую позицию».
| Ситуация | Без requestId |
С requestId |
|---|---|---|
| Таймаут сети, клиент повторил | два ордера | один ордер, второй ответ — 200 с тем же результатом |
| Сервис отправил дубль алерта | два ордера | один ордер |
| Осознанный повтор через сутки | новый ордер | новый ордер (окно идемпотентности — 24 часа) |
{
"accepted": true,
"duplicate": false,
"requestId": "2f0f9b1c-…",
"orderId": "1f0c9d2a-6b41-4f7e-9a02-5c8b1e3d7a44",
"status": "filled",
"filledQty": "0.01",
"avgPrice": "61840.5"
}Исходящие события
Обратное направление: LootX сообщает вашему сервису, что произошло.
<вашМы отправляем событие POST-запросом с теми же заголовками подписи, что ждём от вас. Ответ 2xx считается доставкой; любой другой код или таймаут в 10 секунд — поводом для повтора.
Повторы
Пять попыток с задержками 10 с, 1 мин, 5 мин, 30 мин, 2 ч. После пятой неудачи событие отмечается как недоставленное и остаётся в журнале интеграции — оттуда его можно отправить вручную.
| Событие | Когда | Полезная нагрузка |
|---|---|---|
order.filled |
Заявка исполнена целиком | ордер, цена, комиссия |
order.partially_filled |
Частичное исполнение | ордер, исполненный объём |
position.closed |
Позиция закрыта | итоговый P&L, причина |
stop.triggered |
Сработал стоп | цена срабатывания |
alert.fired |
Сработал ценовой алерт | инструмент, условие |
risk.limit_reached |
Достигнут дневной лимит убытка | лимит, факт |
{
"id": "evt_01J8X…",
"type": "position.closed",
"createdAt": "2026-09-22T11:04:19.442Z",
"data": {
"exchange": "bybit",
"symbol": "BTCUSDT",
"side": "long",
"qty": "0.01",
"entryPrice": "61840.5",
"exitPrice": "62310.0",
"realizedPnl": "4.695",
"reason": "take_profit"
}
}Пример приёмника на Fastify
import Fastify from "fastify";
import crypto from "node:crypto";
const app = Fastify();
const secret = process.env.LOOTX_HOOK_SECRET!;
// Нужно сырое тело: подпись считается от байтов, а не от разобранного объекта.
app.addContentTypeParser("application/json", { parseAs: "buffer" }, (_req, body, done) =>
done(null, body),
);
app.post("/lootx", async (req, reply) => {
const raw = req.body as Buffer;
const ts = String(req.headers["x-lootx-timestamp"] ?? "");
const got = String(req.headers["x-lootx-signature"] ?? "").replace(/^sha256=/, "");
if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) {
return reply.code(401).send({ error: "stale timestamp" });
}
const want = crypto.createHmac("sha256", secret).update(`${ts}.${raw}`).digest("hex");
const ok =
got.length === want.length &&
crypto.timingSafeEqual(Buffer.from(got, "hex"), Buffer.from(want, "hex"));
if (!ok) return reply.code(401).send({ error: "bad signature" });
const event = JSON.parse(raw.toString("utf8"));
// Отвечаем сразу, обрабатываем потом: медленный приёмник провоцирует повторы.
queueMicrotask(() => handle(event));
return reply.code(200).send({ ok: true });
});Telegram и таблицы
Для двух самых частых сценариев готовые каналы есть в кабинете — писать приёмник не нужно.
Интеграции → Telegram → Подключить, дальше бот попросит подтвердить аккаунт. Выберите, какие события слать и в какой чат; поддерживаются личные сообщения и группы, в группе бот должен быть администратором.
Интеграции → Google Sheets. Каждая закрытая сделка добавляется строкой: время, инструмент, направление, объём, цены входа и выхода, комиссия, P&L, комментарий. Удобно для собственной статистики и налоговой отчётности.
Универсальный вариант: узел Webhook как приёмник наших событий и HTTP Request для отправки сигналов нам. Подпись считается узлом Crypto (HMAC SHA256) от строки timestamp.body.
Лимиты
| Направление | Лимит | При превышении |
|---|---|---|
| Входящие вебхуки | 60 запросов/мин на канал | 429, заголовок Retry-After |
| REST, чтение | 600 запросов/мин на ключ | 429 |
| REST, торговля | 120 запросов/мин на ключ | 429 |
| WebSocket-подписки | 200 каналов на соединение | ошибка подписки 4029 |
за последние 90 дней, с учётом повторов
от приёма сигнала до выставленного ордера
Чек-лист перед боем
Секреты не в коде
Секрет вебхука и API-ключ — в переменных окружения или менеджере секретов, а не в репозитории.
Тестовый режим пройден
Канал отработал на тестовом режиме, в журнале интеграции видны ожидаемые действия.
Стоп в каждом сигнале
stopLoss передаётся всегда, а в правилах канала включён обязательный стоп.
Ограничения выставлены
Максимальный объём, белый список инструментов и дневной лимит убытка настроены так, что ошибка в вашем коде не превратится в потерю депозита.
Есть наблюдение
Событие risk.limit_reached и недоставленные вебхуки приходят туда, где вы их увидите.
https://docs.lootx.trade/en/api/integrations
3Счёт, баланс и позиции
Чтение состояния аккаунта: доступные средства, открытые позиции, плечо.
Состояние счёта читается одним запросом на биржу. LootX нормализует ответ: у Bybit, Binance и OKX разные названия полей и разные единицы, здесь они одинаковые.
/v1/accountБаланс и доступные средства по всем подключённым биржам или по одной.
Параметры
| Параметр | Тип | Описание |
|---|---|---|
exchange |
string | Код биржи. Без него — все подключённые |
currency |
string | Валюта расчёта, по умолчанию USDT |
{
"items": [
{
"exchange": "bybit",
"accountType": "UNIFIED",
"equity": "1284.41",
"available": "1140.02",
"usedMargin": "144.39",
"unrealizedPnl": "12.77",
"currency": "USDT"
}
]
}Позиции
/v1/positionsОткрытые позиции. Закрытые сюда не попадают — для них есть история сделок.
Поля ответа
| Поле | Описание |
|---|---|
symbol |
Инструмент |
side |
long или short |
qty |
Размер позиции в контрактах |
entryPrice |
Средняя цена входа |
markPrice |
Маркировочная цена биржи |
liquidationPrice |
Цена ликвидации, null если её нет |
unrealizedPnl |
Нереализованный результат |
leverage |
Плечо инструмента |
Плечо
/v1/positions/leverageМеняет плечо инструмента. Биржи по-разному относятся к смене плеча при открытой позиции: часть запрещает, часть пересчитывает маржу. Ответ 422 leverage_change_rejected означает, что отказала биржа, а не мы.
https://docs.lootx.trade/en/api/rest-account
4Ордера
Создание, изменение и отмена заявок, чтение истории исполнений.
Создание заявки
/v1/ordersСоздаёт заявку. Идемпотентен по requestId — подробности в разделе Интеграция с сервисами.
Тело
| Поле | Тип | Обязательно | Описание |
|---|---|---|---|
exchange |
string | да | Код биржи |
symbol |
string | да | Инструмент |
side |
string | да | buy / sell |
type |
string | да | market / limit / stop_market / stop_limit |
qty |
string | да | Объём |
price |
string | для limit | Цена |
triggerPrice |
string | для stop | Цена срабатывания |
stopLoss |
string | нет | Абсолютная цена стопа |
takeProfit |
string | нет | Абсолютная цена тейка |
reduceOnly |
bool | нет | Только уменьшать позицию |
postOnly |
bool | нет | Отменить, если станет тейкером |
requestId |
string | нет, но нужно | Ключ идемпотентности |
curl -X POST https://api.lootx.trade/v1/orders \
-H "Authorization: Bearer $LOOTX_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"exchange": "bybit",
"symbol": "BTCUSDT",
"side": "buy",
"type": "limit",
"qty": "0.01",
"price": "61500",
"postOnly": true,
"requestId": "a1b2c3"
}'const order = await lootx.post("/v1/orders", {
exchange: "bybit",
symbol: "BTCUSDT",
side: "buy",
type: "limit",
qty: "0.01",
price: "61500",
postOnly: true,
requestId: crypto.randomUUID(),
});Изменение и отмена
/v1/orders/:idМеняет цену и/или объём активной заявки. Реализовано как cancel/replace там, где биржа не умеет изменять заявку: id в ответе может отличаться от запрошенного — ориентируйтесь на него, а не на старый.
/v1/orders/:idОтменяет заявку. 404 означает, что заявки уже нет: либо исполнилась, либо отменена раньше. Это не ошибка вашего кода — проверьте историю.
/v1/ordersОтменяет все заявки: по инструменту (symbol), по бирже (exchange) или вообще все. Единственный запрос в API, который мы советуем повесить на горячую клавишу.
История
/v1/fillsИсполнения, от новых к старым. Курсорная пагинация: cursor из nextCursor, окно не больше 31 дня за один проход.
| Параметр | Описание |
|---|---|
from, to |
ISO-8601, границы периода |
symbol |
Фильтр по инструменту |
limit |
До 500 записей за страницу |
https://docs.lootx.trade/en/api/rest-orders
5Каналы и подписки
Что можно слушать в реальном времени и как не потерять соединение.
Один сокет, произвольное число подписок: wss://api.lootx.trade/v1/stream.
Подключение
const ws = new WebSocket("wss://api.lootx.trade/v1/stream");
ws.onopen = () => {
ws.send(JSON.stringify({ op: "auth", token: TOKEN }));
ws.send(JSON.stringify({ op: "subscribe", channels: ["book:bybit:BTCUSDT:25"] }));
};
// Пинг раз в 20 секунд — иначе сервер закроет соединение по таймауту.
setInterval(() => ws.readyState === 1 && ws.send(JSON.stringify({ op: "ping" })), 20_000);Каналы
| Канал | Формат | Что приходит |
|---|---|---|
trades |
trades:<биржа>:<символ> |
Каждая сделка: цена, объём, сторона |
book |
book:<биржа>:<символ>:<глубина> |
Стакан: снимок, затем изменения |
kline |
kline:<биржа>:<символ>:<тф> |
Свечи, включая незакрытую |
orders |
orders |
Ваши заявки: создание, исполнение, отмена |
positions |
positions |
Изменения позиций |
account |
account |
Баланс и маржа |
Стакан: снимок и дельты
Первым сообщением канала book приходит полный снимок ("type": "snapshot"), дальше — только изменившиеся уровни ("type": "delta"). Уровень с нулевым объёмом означает, что уровень исчез.
{
"channel": "book:bybit:BTCUSDT:25",
"type": "delta",
"seq": 88123,
"data": { "bids": [["61840.5", "0.42"], ["61840.0", "0"]], "asks": [] }
}Обрывы
Переподключайтесь с задержкой
1 с, 2 с, 4 с… до 30 с, со случайным разбросом. Десять клиентов, синхронно ломящихся после сетевого сбоя, — это ваш собственный DDoS.
Повторяйте подписки
Сервер не помнит, на что вы были подписаны. После auth отправьте subscribe заново.
Сверьтесь с REST
После долгого обрыва прочитайте позиции и заявки через REST: события, случившиеся без соединения, не будут повторены.
https://docs.lootx.trade/en/api/ws-channels
6Ошибки и лимиты
Формат ошибки, коды, лимиты запросов и правильная стратегия повторов.
Любая ошибка API возвращается одинаково — HTTP-кодом и телом:
{
"error": {
"code": "insufficient_balance",
"message": "Недостаточно средств для открытия позиции",
"details": { "required": "120.00", "available": "84.31" },
"requestId": "req_01J8X…"
}
}Коды
| HTTP | code |
Что делать |
|---|---|---|
| 400 | invalid_request |
Починить тело запроса; повтор не поможет |
| 401 | unauthorized |
Проверить токен и его срок |
| 401 | bad_signature |
Пересчитать HMAC от сырого тела |
| 403 | scope_required |
У ключа нет нужного права |
| 404 | not_found |
Проверить идентификатор |
| 409 | duplicate_request |
Это не ошибка: requestId уже обработан |
| 422 | insufficient_balance |
Уменьшить объём |
| 422 | symbol_not_tradable |
Инструмент недоступен для торговли |
| 429 | rate_limited |
Подождать Retry-After, затем повторить |
| 502 | exchange_unavailable |
Биржа не отвечает — повторить с задержкой |
| 503 | maintenance |
Плановые работы, см. Retry-After |
Повторы
async function withRetry<T>(fn: () => Promise<T>, tries = 5): Promise<T> {
let lastError: unknown;
for (let i = 0; i < tries; i++) {
try {
return await fn();
} catch (e) {
lastError = e;
const status = (e as { status?: number }).status ?? 0;
if (status && status < 500 && status !== 429) throw e; // чинить, а не повторять
const base = Math.min(1000 * 2 ** i, 30_000);
await new Promise((r) => setTimeout(r, base * (0.7 + Math.random() * 0.6)));
}
}
throw lastError;
}https://docs.lootx.trade/en/api/errors
7Справочник вебхуков
Все события, их поля и порядок доставки.
Справочник по исходящим событиям. Как их принимать и проверять подпись — в разделе Интеграция с сервисами.
Общая оболочка
| Поле | Тип | Описание |
|---|---|---|
id |
string | Идентификатор события, уникален навсегда |
type |
string | Тип события |
createdAt |
string | ISO-8601, UTC |
data |
object | Полезная нагрузка, зависит от типа |
order.filled
{
"orderId": "1f0c9d2a-…",
"exchange": "bybit",
"symbol": "BTCUSDT",
"side": "buy",
"orderType": "limit",
"qty": "0.01",
"avgPrice": "61840.5",
"fee": "0.0371",
"feeCurrency": "USDT",
"source": "webhook",
"comment": "breakout long"
}position.closed
{
"exchange": "bybit",
"symbol": "BTCUSDT",
"side": "long",
"qty": "0.01",
"entryPrice": "61840.5",
"exitPrice": "62310.0",
"realizedPnl": "4.695",
"fee": "0.0742",
"reason": "take_profit",
"openedAt": "2026-09-22T10:41:02.120Z"
}Возможные reason: manual, take_profit, stop_loss, liquidation, opposite_signal.
risk.limit_reached
{
"limit": "daily_loss",
"threshold": "150.00",
"actual": "151.42",
"action": "trading_locked"
}https://docs.lootx.trade/en/api/webhooks