API-ключ — это пара из публичного ключа (publicKey) и секретного (secretKey). Он заменяет логин и пароль при программном доступе: публичный ключ уходит в заголовке запроса, секретным этот запрос подписывается. Так бот или внешний сервис работает от вашего имени, не зная пароля от аккаунта.
Секретный ключ показывается только один раз
secretKeyотдаётся в момент создания и на серверах биржи не хранится — восстановить его нельзя ни через поддержку, ни как-то ещё. Не сохранили — удалите ключ и выпустите новый.
Как создать ключ
- Откройте Профиль → API ключи и нажмите Создать ключ.
- Придумайте название — по нему вы будете отличать ключи друг от друга.
- Выберите уровень доступа для каждой категории и, если нужно, укажите IP-адреса (см. ниже).
- Подтвердите создание кодом 2FA.
- Скопируйте
secretKeyпрежде, чем закроете окно.
В форме создания права разбиты на две группы: для счетов (Фиатный процессинг, Криптопроцессинг, Переводы) и для трейдинга (Торговля спот, Торговля фьючерсы). По умолчанию везде стоит «Нет» — включайте только то, что нужно вашему боту.
Одновременно у вас может быть до 10 активных ключей. Выпустить или удалить ключ можно только под полноценным входом в аккаунт — с паролем и 2FA. Сам API-ключ такого права не имеет ни при каких настройках: даже утёкшим ключом нельзя выпустить себе новый.
Через REST
Ключ можно выпустить и запросом — но тоже только под входом в аккаунт (JWT + 2FA), не API-ключом:
POST https://hub.abcex.io/api/v1/identity/client/api-key/create
Authorization: Bearer <JWT из логина>
Content-Type: application/json
{
"name": "my-trading-bot",
"auth2fa_code": "123456",
"permissions": [
{ "category": "trading_spot", "accessLevel": "full" },
{ "category": "trading_futures", "accessLevel": "full" },
{ "category": "dictionary", "accessLevel": "read_only" },
{ "category": "transfer", "accessLevel": "full" },
{ "category": "fiat_processing", "accessLevel": "none" },
{ "category": "crypto_processing", "accessLevel": "none" },
{ "category": "p2p", "accessLevel": "none" }
],
"ipAddresses": ["1.2.3.4"]
}Ответ:
{
"publicKey": "94becfa6dc2ded...",
"secretKey": "89ba273ce5ec52..."
}Права ключа
Права устроены не как один переключатель «читать / торговать / выводить». Для каждой категории операций вы отдельно выбираете один из трёх уровней. Поэтому ключ для бота может полноценно торговать на споте и при этом вообще не иметь доступа к выводу средств.
| Уровень в ЛК | Код в API | Что можно |
|---|---|---|
| Нет | none | Ничего. Стоит по умолчанию — всё, что боту не нужно, так и оставляйте. |
| Чтение | read_only | Только просмотр: балансы, ордера, история. Изменить ничего нельзя. |
| Полные | full | Просмотр плюс операции: создать и отменить ордер, перевод, вывод. |
Уровень выбирается для каждой категории:
| Категория в ЛК | Код в API | Что охватывает | Нужен whitelist IP |
|---|---|---|---|
| Торговля спот | trading_spot | Ордера по спотовым парам | Нет |
| Торговля фьючерсы | trading_futures | Ордера и позиции по фьючерсам | Нет |
| Переводы | transfer | Переводы между своими счетами на бирже | Нет |
| Справочники | dictionary | Инструменты, тикеры, коды валют | Нет |
| Фиатный процессинг | fiat_processing | Пополнение и вывод рублей | Да |
| Криптопроцессинг | crypto_processing | Кошельки, ввод и вывод криптовалюты | Да |
| P2P | p2p | Сделки на P2P-платформе | Да |
Денежным категориям whitelist IP обязателенФиатный процессинг, Криптопроцессинг и P2P двигают деньги за пределы счёта. Пока у ключа не задан список IP-адресов, им можно поставить только уровень «Нет».
Whitelist IP и срок жизни ключа
От списка доверенных IP-адресов зависит и срок жизни ключа, и то, какие права ему можно выдать.
| Без whitelist IP | С whitelist IP | |
|---|---|---|
| Срок жизни ключа | 90 дней | Бессрочно |
| Откуда принимаются запросы | С любого IP | Только с указанных |
| Торговля, переводы, справочники | Доступны | Доступны |
| Ввод, вывод и P2P | Недоступны | Доступны |
Простое правило: боту на своём сервере со статическим IP whitelist нужен всегда — ключ не истекает, и права можно выдать любые. Ключ без whitelist подходит для временных задач и аналитики.
Ключ без whitelist перестанет работать через 90 днейРовно через 90 дней после создания такой ключ истекает, и каждый запрос получает отказ
api key expired. Предупреждения перед этим не приходит — задайте whitelist или поставьте себе напоминание заменить ключ.
Как отправить запрос с ключом
Запросы с API-ключом идут на шлюз https://api.abcex.io. Пути и тела запросов те же, что в остальной документации (/api/v1/..., /api/v2/exchange/...), — отличаются только заголовки.
Заголовки
К каждому запросу добавьте три заголовка. Заголовок Authorization не нужен.
| Заголовок | Что передать |
|---|---|
X-API-Key | Ваш publicKey (64 hex-символа) |
X-API-Timestamp | Текущее Unix-время в миллисекундах, строкой |
X-API-Signature | Подпись запроса — как её посчитать, см. ниже |
Подпись
Подпись доказывает бирже, что запрос отправили вы и его никто не изменил по дороге. Считается в три шага:
- Возьмите текущее время в миллисекундах — это
timestamp. Его же отправьте вX-API-Timestamp. - Соберите строку из четырёх частей, разделённых переводом строки
\n: время, метод, путь с query-string, тело. - Посчитайте от этой строки HMAC-SHA256 с ключом
secretKeyи переведите результат в hex — это и естьX-API-Signature.
message = timestamp + "\n" + METHOD + "\n" + path + "\n" + body
signature = HMAC-SHA256(secretKey, message) → hex-строка (64 символа)
Например, для GET /api/v1/currency-network/list?limit=10 строка для подписи выглядит так (последняя строка пустая — у GET нет тела):
1700000000000
GET
/api/v1/currency-network/list?limit=10
Правила, на которых чаще всего ошибаются:
timestamp— та же самая строка, что уходит вX-API-Timestamp.METHOD— заглавными буквами:GET,POST,PATCH,DELETE,PUT.path— путь вместе с query-string, ровно как в URL:/api/v1/orders?limit=10&side=buy.body— тело запроса байт-в-байт, та же строка, что отправляется. Для GET/DELETE без тела — пустая строка"".- Разделитель — настоящий перевод строки
\n(0x0A), не\r\n. secretKeyподаётся в HMAC как hex-строка, как есть — не декодируйте её в байты.- Окно времени — биржа принимает запрос, если его время отличается от серверного не больше чем на 30 секунд. Считайте
timestampпрямо перед отправкой.
Пример на Node.js
const crypto = require('node:crypto');
const BASE_URL = 'https://api.abcex.io';
const PUBLIC_KEY = '<publicKey>';
const SECRET_KEY = '<secretKey>';
function sign({ method, path, query, body }) {
const qs = query ? '?' + new URLSearchParams(query).toString() : '';
const fullPath = path + qs;
const bodyStr = body ? JSON.stringify(body) : '';
const timestamp = Date.now().toString();
const message = `${timestamp}\n${method.toUpperCase()}\n${fullPath}\n${bodyStr}`;
const signature = crypto
.createHmac('sha256', SECRET_KEY)
.update(message)
.digest('hex');
return { fullPath, bodyStr, timestamp, signature };
}
async function call({ method, path, query, body }) {
const { fullPath, bodyStr, timestamp, signature } = sign({ method, path, query, body });
const res = await fetch(BASE_URL + fullPath, {
method,
headers: {
'Content-Type': 'application/json',
'X-API-Key': PUBLIC_KEY,
'X-API-Timestamp': timestamp,
'X-API-Signature': signature,
},
body: bodyStr || undefined,
});
return { status: res.status, body: await res.text() };
}
(async () => {
// GET
console.log(await call({ method: 'GET', path: '/api/v1/auth/me' }));
// GET с query
console.log(await call({
method: 'GET',
path: '/api/v1/currency-network/list',
query: { limit: 10 },
}));
})();Пример на Python
import hmac, hashlib, time, json, urllib.parse, requests
BASE_URL = 'https://api.abcex.io'
PUBLIC_KEY = '<publicKey>'
SECRET_KEY = '<secretKey>'
def call(method, path, query=None, body=None):
qs = '?' + urllib.parse.urlencode(query) if query else ''
full_path = path + qs
body_str = json.dumps(body, separators=(',', ':')) if body else ''
timestamp = str(int(time.time() * 1000))
message = f'{timestamp}\n{method.upper()}\n{full_path}\n{body_str}'
signature = hmac.new(SECRET_KEY.encode(), message.encode(), hashlib.sha256).hexdigest()
return requests.request(
method,
BASE_URL + full_path,
data=body_str if body_str else None,
headers={
'Content-Type': 'application/json',
'X-API-Key': PUBLIC_KEY,
'X-API-Timestamp': timestamp,
'X-API-Signature': signature,
},
)WebSocket
Тот же ключ подходит для WebSocket. Подпись здесь проще: подписывается только вход, а не каждое сообщение.
URL: wss://hub.abcex.io/websocket/exchange
Вход
Первым сообщением после открытия соединения отправьте:
{
"method": "auth",
"key": "<publicKey>",
"timestamp": 1700000000000,
"signature": "<hex HMAC-SHA256>"
}Подпись — HMAC-SHA256 с ключом secretKey от строки из времени и публичного ключа:
message = timestamp + "\n" + publicKey
signature = HMAC-SHA256(secretKey, message) → hex
timestamp — Unix-время в миллисекундах, числом. Окно — ±30 секунд.
Ответ: { "method": "auth", "success": true } или { "method": "auth", "success": false, "error": "<код>" }. Коды ошибок: invalid_key_format, invalid_signature, timestamp_out_of_range, key_not_found, key_expired.
Подписки
{ "id": 1, "method": "subscribe", "data": ["BTCUSDT@depth"] }
{ "id": 2, "method": "unsubscribe", "data": ["BTCUSDT@depth"] }
{ "id": 3, "method": "list_subscriptions" }Публичные каналы доступны и без входа, приватные — только после него.
| Канал | Тип | Что |
|---|---|---|
<SYMBOL>@depth | public | стакан |
<SYMBOL>@trades | public | лента сделок |
<SYMBOL>.1m@candles | public | свечи |
<SYMBOL>@ticker | public | тикер |
CLNTF@my-balances | private | спотовые балансы |
HLORD@my-balances | private | фьючерсные балансы |
<SYMBOL>@order-info | private | ордера |
<SYMBOL>@order-history | private | история ордеров |
<SYMBOL>@my-trades | private | свои сделки |
position-info | private | позиции |
Ping
Соединение нужно поддерживать самому: отправляйте { "method": "ping" } каждые 20–25 секунд, в ответ придёт { "pong": <ms> }. Если от клиента около 30 секунд ничего не приходит, сервер закрывает соединение.
Пример на Node.js 18/20
npm install wsconst crypto = require('node:crypto');
const WebSocket = require('ws');
const WS_URL = 'wss://hub.abcex.io/websocket/exchange';
const PUBLIC_KEY = '<publicKey>';
const SECRET_KEY = '<secretKey>';
const ws = new WebSocket(WS_URL);
ws.on('open', () => {
const ts = Date.now();
const signature = crypto
.createHmac('sha256', SECRET_KEY)
.update(`${ts}\n${PUBLIC_KEY}`)
.digest('hex');
ws.send(JSON.stringify({ method: 'auth', key: PUBLIC_KEY, timestamp: ts, signature }));
});
ws.on('message', (raw) => {
const msg = JSON.parse(raw.toString());
console.log('<-', msg);
if (msg.method === 'auth' && msg.success) {
ws.send(JSON.stringify({
id: 1,
method: 'subscribe',
data: ['CLNTF@my-balances', 'BTCUSDT@depth'],
}));
setInterval(() => ws.send(JSON.stringify({ method: 'ping' })), 25_000);
}
});
ws.on('close', (code) => console.log('closed', code));
ws.on('error', (err) => console.error('error', err));На Node.js ≥ 22 и в браузере WebSocket встроен — тогда без npm install ws, с addEventListener и event.data вместо raw.
Если ключ не работает
Почти все отказы по ключу приходят с кодом 401, а причина — текстом в теле ответа. По ней сразу понятно, что чинить.
| Ответ биржи | Что это значит |
|---|---|
ip address not in whitelist | Запрос пришёл с IP, которого нет в списке ключа. Обычно сервер сменил адрес или он динамический. |
api key expired | Истёк срок ключа — 90 дней у ключа без whitelist. Задайте whitelist или выпустите новый ключ. |
timestamp expired | Часы на вашей машине разошлись с биржей больше чем на 30 секунд. Синхронизируйте время по NTP. |
insufficient permissions for this route | Ключу не хватает уровня в нужной категории — например, стоит «Чтение», а нужны «Полные». |
invalid signature | Подпись не совпала — см. чек-лист ниже. |
api key not found | Такого ключа нет — удалён или publicKey скопирован с ошибкой. |
api key is not active | Ключ заблокирован или удалён. |
invalid API key format | publicKey не 64 hex-символа — обычно при копировании потерялась часть строки. |
missing X-API-Timestamp or X-API-Signature | В запросе нет одного из заголовков. |
invalid timestamp format | В X-API-Timestamp не число. |
route not allowed for API key access | Этот эндпоинт закрыт для API-ключей. Если он вам нужен — обратитесь в поддержку. |
Чек-лист при invalid signature
invalid signature- Метод — заглавными буквами.
- Путь в подписи и в URL — одинаковый, вместе с query-string.
- Тело в подписи — байт-в-байт то же, что отправляется. Не сериализуйте его дважды: порядок полей может поменяться.
- GET без тела: в подписи пустая строка
"", в запросе нетbody. - Разделитель —
\n(0x0A), не\r\nи не литерал\\n. secretKey— hex-строка как есть, без декодирования в байты.timestampв подписи и в заголовке — одно и то же значение.- Часы на клиенте не расходятся с реальным временем больше чем на 30 секунд.
Безопасность
Секретный ключ — это доступ к вашим деньгамНе передавайте
secretKeyникому, не коммитьте его в git и не пишите в логи. При любом подозрении на утечку сразу удалите ключ в личном кабинете и выпустите новый — удалённый ключ перестаёт работать немедленно.
- Выдавайте минимум прав. Боту, который собирает статистику, хватит «Чтения». Торговому боту не нужны права на ввод и вывод — он торгует, а не выводит.
- На каждую задачу — свой ключ. Отдельный для бота, отдельный для аналитики. Если один придётся отозвать, остальное продолжит работать.
- Ограничивайте по IP. Тогда утёкшим ключом с чужого сервера воспользоваться не смогут.
- Сначала проверьте на «Чтении». Обкатайте бота на ключе только с чтением и на минимальных суммах, и лишь потом выдавайте «Полные».
Если подключаете сторонний сервисВводя ключ в чужое приложение, вы отдаёте ему все права этого ключа. Выдавайте только необходимый минимум и указывайте whitelist с IP-адресами именно этого сервиса.
