Skip to main content

KuCoin API JavaScript tutorial for Node.js: REST and WebSocket

Public and private REST calls, Classic WebSocket streams and a safe test order with kucoin-api

Updated 5 min readMarkdown

Private KuCoin REST requests need an API key, a timestamp, an HMAC signature, an HMAC-signed passphrase and the key version. Classic WebSocket connections need a connection token first, and each subscription has to reach the right product connection. This tutorial walks through kucoin-api, an MIT-licensed Node.js, JavaScript and TypeScript SDK for KuCoin, from install to a test order that never reaches the matching engine.

The SDK comes from the same maintainers as the binance, bybit-api and okx-api JS SDKs, with over 4.5 million npm downloads across 10+ SDKs. The overview lives in the KuCoin JavaScript SDK docs, and the full walkthrough (Futures test orders, the WebSocket API, reconnect recovery, regional hosts and proxies) is the KuCoin API JavaScript tutorial for Node.js.

What you get

Client Use it for
SpotClient Classic Spot, Margin, funding, transfer, Earn and Convert REST calls
FuturesClient Classic Futures market, account, order, position and funding REST calls
WebsocketClient Classic and Pro public and private WebSocket subscriptions
WebsocketAPIClient Promise-based Spot, Margin and Futures order commands over WebSocket

Start with the Classic clients unless you specifically need the newer Unified Account or Pro APIs.

Install and API keys

npm install kucoin-api

Public market data needs no credentials. Private calls, private streams and order commands need three values: KUCOIN_API_KEY, KUCOIN_API_SECRET and KUCOIN_API_PASSPHRASE. The passphrase is the one you chose when creating the key, not your account password.

Use a General-only key while you build account views, and add Spot or Futures permission only when you start testing orders. Nothing here needs Withdrawal permission. Keep keys server-side and out of logs.

A few naming details save time later: Spot symbols look like BTC-USDT, Futures symbols like XBTUSDTM, and a Futures size is a number of contracts rather than an amount of BTC. Successful REST responses come back as { code: '200000', data }.

1. Public REST

import { SpotClient } from 'kucoin-api';

const client = new SpotClient();

async function main() {
  const time = await client.getServerTime();
  console.log('Server time:', time.data);

  const ticker = await client.getTicker({ symbol: 'BTC-USDT' });
  console.log('BTC-USDT ticker:', ticker.data);

  const book = await client.getOrderBookLevel20({ symbol: 'BTC-USDT' });
  console.log('Best bid:', book.data.bids[0], 'best ask:', book.data.asks[0]);

  const candles = await client.getKlines({ symbol: 'BTC-USDT', type: '1hour' });
  console.log('Latest candle:', candles.data[0]);
}

main().catch(console.error);

Prices and sizes are usually strings. Keep them that way when sending values back to KuCoin, and convert deliberately when you calculate.

2. Private REST

The same client takes credentials and signs every request.

import { SpotClient } from 'kucoin-api';

const client = new SpotClient({
  apiKey: process.env.KUCOIN_API_KEY,
  apiSecret: process.env.KUCOIN_API_SECRET,
  apiPassphrase: process.env.KUCOIN_API_PASSPHRASE,
});

async function main() {
  const balances = await client.getBalances({ type: 'trade' });
  console.log('Spot trading balances:', balances.data);

  const openOrders = await client.getHFActiveOrdersPaginated({
    symbol: 'BTC-USDT',
    pageNum: 1,
    pageSize: 20,
  });
  console.log('Open orders:', openOrders.data.items);
}

main().catch(console.error);

The client throws when KuCoin returns a non-success business code. Parsed errors carry the HTTP status, body, headers and request context, with the key, secret and passphrase left out. Methods with HF in the name map to KuCoin's current high-frequency Spot endpoints.

3. Public WebSocket streams

Classic subscriptions take a topic and a wsKey. The SDK fetches the connection token, opens the right connection, keeps it alive and resubscribes after a reconnect.

import { WebsocketClient, WS_KEY_MAP } from 'kucoin-api';

const ws = new WebsocketClient();

ws.on('update', (update) => console.log(update.wsKey, update.topic, update.data));
ws.on('response', (response) => console.log('response', response.type));
ws.on('reconnected', ({ wsKey }) => console.log('reconnected', wsKey));
ws.on('exception', (error) => console.error('ws error', error));

ws.subscribe('/market/ticker:BTC-USDT', WS_KEY_MAP.spotPublicV1);
ws.subscribe('/contractMarket/tickerV2:XBTUSDTM', WS_KEY_MAP.futuresPublicV1);

process.once('SIGINT', () => ws.closeAll());

A welcome response means the connection is ready, and an ack confirms the subscription. Market data arrives through update.

4. Private streams

Spot and Futures private topics use separate connections, so keep the wsKey with every event.

import { WebsocketClient, WS_KEY_MAP } from 'kucoin-api';

const ws = new WebsocketClient({
  apiKey: process.env.KUCOIN_API_KEY,
  apiSecret: process.env.KUCOIN_API_SECRET,
  apiPassphrase: process.env.KUCOIN_API_PASSPHRASE,
});

ws.on('update', (update) => console.log('private', update.wsKey, update.topic));
ws.on('exception', (error) => console.error('ws error', error));

ws.subscribe(['/spotMarket/tradeOrdersV2', '/account/balance'], WS_KEY_MAP.spotPrivateV1);
ws.subscribe(
  ['/contractMarket/tradeOrders:XBTUSDTM', '/contractAccount/wallet'],
  WS_KEY_MAP.futuresPrivateV1,
);

5. A Spot test order

KuCoin's test-order endpoint checks authentication and order fields without touching the matching engine. Read the symbol metadata first so the size meets the minimums.

import { SpotClient } from 'kucoin-api';

const client = new SpotClient({
  apiKey: process.env.KUCOIN_API_KEY,
  apiSecret: process.env.KUCOIN_API_SECRET,
  apiPassphrase: process.env.KUCOIN_API_PASSPHRASE,
});

async function main() {
  const { data: symbol } = await client.getSymbol({ symbol: 'BTC-USDT' });
  const { data: ticker } = await client.getTicker({ symbol: 'BTC-USDT' });

  const price = Number(ticker.bestBid);
  const step = Number(symbol.baseIncrement);
  const minSize = Math.max(Number(symbol.baseMinSize), Number(symbol.minFunds) / price);
  const decimals = (symbol.baseIncrement.split('.')[1] || '').length;
  const size = (Math.ceil(minSize / step) * step).toFixed(decimals);

  const result = await client.submitHFOrderTest({
    clientOid: client.generateNewOrderID(),
    symbol: 'BTC-USDT',
    side: 'buy',
    type: 'limit',
    price: ticker.bestBid,
    size,
    timeInForce: 'GTC',
    postOnly: true,
  });
  console.log('Spot order test:', result);
}

main().catch(console.error);

The test endpoint is good for checking credentials, permissions, signing and order fields. It doesn't test matching, fills, cancellation or private order updates; the full tutorial covers those with the WebSocket API.

Where to go next

More docs

API reference and usage guides also live on the Siebly docs portal (Hyperliquid, Bybit and Binance pages so far).

No comments yet. Be the first to comment.