API ключи

API-ключ — это пара из публичного ключа (publicKey) и секретного (secretKey). Он заменяет логин и пароль при программном доступе: публичный ключ уходит в заголовке запроса, секретным этот запрос подписывается. Так бот или внешний сервис работает от вашего имени, не зная пароля от аккаунта.

🚧

Секретный ключ показывается только один раз

secretKey отдаётся в момент создания и на серверах биржи не хранится — восстановить его нельзя ни через поддержку, ни как-то ещё. Не сохранили — удалите ключ и выпустите новый.


Как создать ключ

  1. Откройте Профиль → API ключи и нажмите Создать ключ.
  2. Придумайте название — по нему вы будете отличать ключи друг от друга.
  3. Выберите уровень доступа для каждой категории и, если нужно, укажите IP-адреса (см. ниже).
  4. Подтвердите создание кодом 2FA.
  5. Скопируйте secretKey прежде, чем закроете окно.
Профиль → «API ключи»: счётчик активных ключей, кнопка «Создать ключ» и ссылка на документацию API

В форме создания права разбиты на две группы: для счетов (Фиатный процессинг, Криптопроцессинг, Переводы) и для трейдинга (Торговля спот, Торговля фьючерсы). По умолчанию везде стоит «Нет» — включайте только то, что нужно вашему боту.

Создание ключа: название, затем уровень доступа отдельно по каждой категории

Одновременно у вас может быть до 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Кошельки, ввод и вывод криптовалютыДа
P2Pp2pСделки на 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Подпись запроса — как её посчитать, см. ниже

Подпись

Подпись доказывает бирже, что запрос отправили вы и его никто не изменил по дороге. Считается в три шага:

  1. Возьмите текущее время в миллисекундах — это timestamp. Его же отправьте в X-API-Timestamp.
  2. Соберите строку из четырёх частей, разделённых переводом строки \n: время, метод, путь с query-string, тело.
  3. Посчитайте от этой строки 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>@depthpublicстакан
<SYMBOL>@tradespublicлента сделок
<SYMBOL>.1m@candlespublicсвечи
<SYMBOL>@tickerpublicтикер
CLNTF@my-balancesprivateспотовые балансы
HLORD@my-balancesprivateфьючерсные балансы
<SYMBOL>@order-infoprivateордера
<SYMBOL>@order-historyprivateистория ордеров
<SYMBOL>@my-tradesprivateсвои сделки
position-infoprivateпозиции

Ping

Соединение нужно поддерживать самому: отправляйте { "method": "ping" } каждые 20–25 секунд, в ответ придёт { "pong": <ms> }. Если от клиента около 30 секунд ничего не приходит, сервер закрывает соединение.

Пример на Node.js 18/20

npm install ws
const 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 formatpublicKey не 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

  1. Метод — заглавными буквами.
  2. Путь в подписи и в URL — одинаковый, вместе с query-string.
  3. Тело в подписи — байт-в-байт то же, что отправляется. Не сериализуйте его дважды: порядок полей может поменяться.
  4. GET без тела: в подписи пустая строка "", в запросе нет body.
  5. Разделитель — \n (0x0A), не \r\n и не литерал \\n.
  6. secretKey — hex-строка как есть, без декодирования в байты.
  7. timestamp в подписи и в заголовке — одно и то же значение.
  8. Часы на клиенте не расходятся с реальным временем больше чем на 30 секунд.

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

❗️

Секретный ключ — это доступ к вашим деньгам

Не передавайте secretKey никому, не коммитьте его в git и не пишите в логи. При любом подозрении на утечку сразу удалите ключ в личном кабинете и выпустите новый — удалённый ключ перестаёт работать немедленно.

  • Выдавайте минимум прав. Боту, который собирает статистику, хватит «Чтения». Торговому боту не нужны права на ввод и вывод — он торгует, а не выводит.
  • На каждую задачу — свой ключ. Отдельный для бота, отдельный для аналитики. Если один придётся отозвать, остальное продолжит работать.
  • Ограничивайте по IP. Тогда утёкшим ключом с чужого сервера воспользоваться не смогут.
  • Сначала проверьте на «Чтении». Обкатайте бота на ключе только с чтением и на минимальных суммах, и лишь потом выдавайте «Полные».
📘

Если подключаете сторонний сервис

Вводя ключ в чужое приложение, вы отдаёте ему все права этого ключа. Выдавайте только необходимый минимум и указывайте whitelist с IP-адресами именно этого сервиса.