Back to Gate JavaScript SDK
Gate APISpotMarginUnifiedPerpetual FuturesDeliveryOptionsWebSocket API

Gate API JavaScript Tutorial for Node.js

Build Gate integrations with typed REST API calls, product-specific WebSocket routing, TestNet orders, WebSocket API commands, regional hosts, and proxy support.

first-gate-api-call.mjs
public REST API
import { RestClient } from 'gateio-api';

const client = new RestClient();

async function main() {
  try {
    const time = await client.getServerTime();
    console.log('Server time:', time.server_time);
  } catch (error) {
    console.error('Server time request failed:', error);
  }

  try {
    const pair = await client.getSpotCurrencyPair({
      currency_pair: 'BTC_USDT',
    });
    console.log('BTC_USDT metadata:', pair);
  } catch (error) {
    console.error('Currency pair request failed:', error);
  }

  try {
    const tickers = await client.getSpotTicker({
      currency_pair: 'BTC_USDT',
    });
    console.log('BTC_USDT ticker:', tickers[0]);
  } catch (error) {
    console.error('Ticker request failed:', error);
  }

  try {
    const orderBook = await client.getSpotOrderBook({
      currency_pair: 'BTC_USDT',
      limit: 5,
    });
    console.log('Best bid:', orderBook.bids[0]);
    console.log('Best ask:', orderBook.asks[0]);
  } catch (error) {
    console.error('Order book request failed:', error);
  }

  try {
    const candles = await client.getSpotCandles({
      currency_pair: 'BTC_USDT',
      interval: '1h',
      limit: 10,
    });
    console.log('Latest candle:', candles[0]);
  } catch (error) {
    console.error('Candles request failed:', error);
  }
}

main();

API surface map

One Gate SDK, three focused clients

Choose the client that matches the request, stream, or WebSocket command your application needs.

Your app

Bot, dashboard, worker, tool

Any Node.js or JavaScript-compatible service that needs Gate market data, account state, order management, or reconciliation.

npm package

npm install gateio-api

RestClient

Public and private REST API calls

WebsocketClient

Public and private WebSocket subscriptions

WebsocketAPIClient

Awaitable Spot and Futures order commands

Gate API

REST API calls across Gate product groups

Public market streams

Private account streams

WebSocket API order commands

Request routing

Product, account, settlement currency, and connection are separate choices

Keep these values explicit so each request reaches the intended Gate product, account, environment, and WebSocket connection.

Spot market

currency_pair: 'BTC_USDT'

Select the Spot or margin currency pair.

Account

account: 'spot'

Select the account used by a Spot order or query.

Settlement currency

settle: 'usdt'

Select the settlement currency for a Futures or Delivery REST API request.

Contract

contract: 'BTC_USDT'

Select the exact Futures, Delivery, or Options contract.

WebSocket connection

WS_KEY_MAP.perpFuturesUSDTV4

Route a subscription or command to the correct product connection.

API host

baseUrl or wsUrl

Select the live, TestNet, or regional API host.

What this tutorial covers

What to get right in a Gate integration

Start with public data, then add credentials, product routing, private state, TestNet orders, network configuration, and recovery.

REST API and product routing

Use one REST API client while keeping currency pair, account, settlement currency, and contract explicit.

Public and private streams

Subscribe through one WebSocket client and select the correct connection with WS_KEY_MAP.

WebSocket API commands

Await Spot and Futures order commands, inspect the response, then confirm the final order state.

TestNet, hosts, and proxies

Keep live and TestNet credentials separate, select the correct API host, and configure an agent when a proxy is required.

Start building

First REST API calls, streams, TestNet orders, and WebSocket API commands

Run one focused example at a time, then add recovery and production controls around the same clients.

import { RestClient } from 'gateio-api'; const client = new RestClient(); async function main() {  try {    const time = await client.getServerTime();    console.log('Server time:', time.server_time);  } catch (error) {    console.error('Server time request failed:', error);  }   try {    const pair = await client.getSpotCurrencyPair({      currency_pair: 'BTC_USDT',    });    console.log('BTC_USDT metadata:', pair);  } catch (error) {    console.error('Currency pair request failed:', error);  }   try {    const tickers = await client.getSpotTicker({      currency_pair: 'BTC_USDT',    });    console.log('BTC_USDT ticker:', tickers[0]);  } catch (error) {    console.error('Ticker request failed:', error);  }   try {    const orderBook = await client.getSpotOrderBook({      currency_pair: 'BTC_USDT',      limit: 5,    });    console.log('Best bid:', orderBook.bids[0]);    console.log('Best ask:', orderBook.asks[0]);  } catch (error) {    console.error('Order book request failed:', error);  }   try {    const candles = await client.getSpotCandles({      currency_pair: 'BTC_USDT',      interval: '1h',      limit: 10,    });    console.log('Latest candle:', candles[0]);  } catch (error) {    console.error('Candles request failed:', error);  }} main();

Workflow diagrams

REST API, WebSocket stream, and WebSocket API workflows

REST API results, stream updates, and WebSocket API command responses carry different information.

REST API request flow

Choose the product fields and API host, then let RestClient sign private requests and return the endpoint response.

Choose product fields and hostYour code
Call RestClientYour code
Route and signSDK handles
Receive Gate responseGate
Use endpoint-specific resultYour code

WebSocket subscription flow

Choose a topic and wsKey, subscribe, receive the acknowledgement, then process updates.

Choose topic and wsKeyYour code
Call subscribeYour code
Connect and authenticateSDK handles
Receive response eventEvent
Process update eventsYour code

WebSocket API command flow

Await the command response, then confirm final order state through a private stream or REST API query.

Choose Spot or Futures connectionYour code
Await order commandYour code
Match request and responseSDK handles
Inspect header and resultYour code
Confirm final order stateYour code

Production rollout

Before a Gate integration trades unattended

Credentials, product routing, precision, acknowledgements, reconnect recovery, API hosts, and network behavior must all be predictable.

Keep live and TestNet credentials separate, and never put private keys in frontend code.

Keep currency_pair, account, settle, contract, wsKey, and API host explicit in order code and logs.

Set text on application-owned orders so retries and restarts can reconcile them.

Read endpoint-specific response shapes instead of assuming every REST API result is an array.

Treat a WebSocket API response as a command result, then confirm final order state.

Reload balances, positions, open orders, and trades after a private stream gap.

Keep the system clock synchronized and respect Gate rate limits.

Monitor proxy reachability, latency, and egress IP when a proxy is enabled.

Choose your path

Jump to the Gate workflow you are building

Browse all examples

Start with REST API calls

Make public market calls, add credentials, then inspect account and order state.

Open section

Stream live data

Subscribe to public and private Spot and Futures channels.

Open section

Send WebSocket commands

Use WebsocketAPIClient for promise-based Spot and Futures order commands.

Open section

Configure networking

Select the correct API host, then add an HTTP or SOCKS proxy agent when required.

Open section

Build around Gate products and account state

This tutorial covers the Gate API pieces developers usually need first: REST API calls, product routing, public and private streams, TestNet orders, WebSocket API commands, API hosts, proxies, and reconnect recovery.

This tutorial uses gateio-api, Siebly's Node.js, JavaScript, and TypeScript SDK for Gate.com. It covers public and private REST API calls, Spot and Futures TestNet orders, live WebSocket streams, WebSocket API commands, API hosts, and proxies.

The SDK handles REST API request signing, private WebSocket authentication, product-specific WebSocket routing, heartbeats, reconnects, resubscriptions, and WebSocket API response matching. Request and response types are included for TypeScript users.

Key links

Why use gateio-api?

The JavaScript SDK handles all the complexities of integrating with Gate's REST APIs & WebSockets, so you can focus on your system without wasting excessive time on plumbing.

Private Gate REST API requests require a timestamp and HMAC-SHA512 signature. Private WebSocket subscriptions require authentication, and each product family has its own WebSocket connection. A Spot subscription cannot be sent through a USDT perpetual Futures connection.

gateio-api handles those mechanics and gives each job a focused client:

ClientUse it for
RestClientPublic and private REST API calls across Gate product groups
WebsocketClientPublic and private WebSocket subscriptions
WebsocketAPIClientPromise-based Spot and Futures order commands over WebSocket

WebsocketClient also handles heartbeats, reconnects, cached subscriptions, and resubscription. WebsocketAPIClient adds login, request IDs, response matching, and promise resolution.

Install the SDK

npm install gateio-api

The package includes CommonJS, ESM, and TypeScript declarations. Every example below is plain JavaScript using ESM imports.

Create Gate API keys

Public market data does not require credentials. Private REST API calls, private streams, and WebSocket API commands require an API key and secret.

Create and manage keys from the Gate API key page. Start with the least access your application needs:

  • Use a read-only key while building account views and recovery code.
  • Enable Spot read-write access only for Spot order workflows.
  • Enable perpetual-contract read-write access only for Futures order workflows.
  • Add an IP whitelist when your outbound IP is stable.
  • Keep live and TestNet credentials separate.
  • Allow a few minutes for API key and permission changes to take effect.
  • Do not enable withdrawal permission for anything in this tutorial.

Set these live credential names in your shell, secret manager, or deployment environment:

  • GATE_API_KEY
  • GATE_API_SECRET

Use separate names for TestNet:

  • GATE_TESTNET_API_KEY
  • GATE_TESTNET_API_SECRET
  • GATE_PLACE_TESTNET_ORDER=true

The order examples check GATE_PLACE_TESTNET_ORDER before sending an order. Do not log API keys or secrets, and never put them in browser code.

Gate products and request vocabulary

Several short fields determine where a request goes and what its values mean:

FieldExampleMeaning
currency_pairBTC_USDTA Spot or margin market
accountspotThe account used by a Spot order or query
settleusdtThe settlement currency for a Futures or Delivery REST API request
contractBTC_USDTThe exact Futures, Delivery, or Options contract
size1 or -1Futures contract count, not a BTC or USDT amount
textt-my-order-123Your custom order ID
wsKeyWS_KEY_MAP.perpFuturesUSDTV4The WebSocket key that selects a product connection

For Spot orders, account can be spot, margin, cross_margin, or unified, depending on the account and endpoint. This tutorial uses spot.

For USDT perpetual Futures, use settle: 'usdt' and a contract such as BTC_USDT. The size is a number of contracts. In one-way mode, a positive size buys and a negative size sells. The contract's quanto_multiplier tells you how much underlying currency one contract represents.

Keep these routing values visible in code and logs. They are part of the request, not interchangeable labels.

Start building: first calls and streams

Run each example on its own. Start with public data, then add authenticated account state and live streams.

1. Make public REST API calls

Public calls need no API key. Gate response bodies are endpoint-specific: server time and order books are objects, tickers are arrays, and candles are tuple arrays.

import { RestClient } from 'gateio-api';

const client = new RestClient();

async function main() {
  try {
    const time = await client.getServerTime();
    console.log('Server time:', time.server_time);
  } catch (error) {
    console.error('Server time request failed:', error);
  }

  try {
    const pair = await client.getSpotCurrencyPair({
      currency_pair: 'BTC_USDT',
    });
    console.log('BTC_USDT metadata:', pair);
  } catch (error) {
    console.error('Currency pair request failed:', error);
  }

  try {
    const tickers = await client.getSpotTicker({
      currency_pair: 'BTC_USDT',
    });
    console.log('BTC_USDT ticker:', tickers[0]);
  } catch (error) {
    console.error('Ticker request failed:', error);
  }

  try {
    const orderBook = await client.getSpotOrderBook({
      currency_pair: 'BTC_USDT',
      limit: 5,
    });
    console.log('Best bid:', orderBook.bids[0]);
    console.log('Best ask:', orderBook.asks[0]);
  } catch (error) {
    console.error('Order book request failed:', error);
  }

  try {
    const candles = await client.getSpotCandles({
      currency_pair: 'BTC_USDT',
      interval: '1h',
      limit: 10,
    });
    console.log('Latest candle:', candles[0]);
  } catch (error) {
    console.error('Candles request failed:', error);
  }
}

main();

The SDK returns the endpoint body directly. There is no shared result wrapper to unwrap.

Each Spot candle is a tuple:

[
  timestamp,
  quoteVolume,
  close,
  high,
  low,
  open,
  baseVolume,
  closed
]

Use the SDK endpoint map or the official endpoint documentation when reading tuple positions and endpoint-specific objects.

2. Make private REST API calls

Private calls use the same RestClient. Add your key and secret, then call private methods normally. The SDK adds the required timestamp and signature.

import { RestClient } from 'gateio-api';

const client = new RestClient({
  apiKey: process.env.GATE_API_KEY,
  apiSecret: process.env.GATE_API_SECRET,
});

async function main() {
  try {
    const balances = await client.getSpotAccounts();
    console.log('Spot balances:', balances);
  } catch (error) {
    console.error('Balance request failed:', error);
  }

  try {
    const openOrders = await client.getSpotOpenOrders({
      account: 'spot',
    });
    console.log('Open Spot orders grouped by pair:', openOrders);
  } catch (error) {
    console.error('Open-order request failed:', error);
  }

  try {
    const recentOrders = await client.getSpotOrders({
      currency_pair: 'BTC_USDT',
      status: 'finished',
      account: 'spot',
      limit: 20,
    });
    console.log('Recent BTC_USDT orders:', recentOrders);
  } catch (error) {
    console.error('Order-history request failed:', error);
  }

  try {
    const trades = await client.getSpotTradingHistory({
      currency_pair: 'BTC_USDT',
      account: 'spot',
      limit: 20,
    });
    console.log('Recent BTC_USDT trades:', trades);
  } catch (error) {
    console.error('Trade-history request failed:', error);
  }
}

main();

getSpotAccounts(), getSpotOrders(), and getSpotTradingHistory() return arrays. getSpotOpenOrders() returns an array whose entries group open orders by currency pair. Other private endpoints return objects or different array shapes, so check the SDK endpoint map instead of assuming a common format.

3. Subscribe to public WebSocket streams

Every subscription includes both a topic and an explicit wsKey. The key tells the SDK which Gate connection should carry the subscription.

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

const ws = new WebsocketClient();

ws.on('response', (response) => {
  console.log('Subscription response:', response);
});

ws.on('update', (update) => {
  console.log('WebSocket update:', update);
});

ws.on('reconnect', ({ wsKey }) => {
  console.log('Reconnecting:', wsKey);
});

ws.on('reconnected', (event) => {
  console.log('Reconnected:', event.wsKey);
});

ws.on('exception', (error) => {
  console.error('WebSocket exception:', error);
});

try {
  ws.subscribe(
    {
      topic: 'spot.tickers',
      payload: ['BTC_USDT'],
    },
    WS_KEY_MAP.spotV4,
  );
} catch (error) {
  console.error('Spot subscription failed:', error);
}

try {
  ws.subscribe(
    {
      topic: 'futures.tickers',
      payload: ['BTC_USDT'],
    },
    WS_KEY_MAP.perpFuturesUSDTV4,
  );
} catch (error) {
  console.error('Futures subscription failed:', error);
}

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

Gate sends Spot ticker data as an object in result, while a Futures ticker update can contain an array. The SDK adds wsKey to each update so one event handler can identify the source connection.

The main WebSocket events are:

EventMeaning
openA connection opened for the reported wsKey
responseSubscription, unsubscription, or request response
updateMarket or account data
authenticatedA private connection authenticated successfully
reconnectThe connection dropped and a reconnect is starting
reconnectedThe connection is open and cached subscriptions were restored
closeA connection closed for the reported wsKey
exceptionThe client or connection reported an error

A successful response event confirms the subscription acknowledgement. It does not mean that a market update has arrived yet.

4. Subscribe to private Spot streams

Private streams use the same client with credentials. This example follows Spot balances, orders, and user trades.

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

const ws = new WebsocketClient({
  apiKey: process.env.GATE_API_KEY,
  apiSecret: process.env.GATE_API_SECRET,
});

ws.on('authenticated', ({ wsKey }) => {
  console.log('Authenticated:', wsKey);
});

ws.on('response', (response) => {
  console.log('Subscription response:', response);
});

ws.on('update', (update) => {
  console.log('Private Spot update:', update);
});

ws.on('reconnected', (event) => {
  console.log('Reconnected:', event.wsKey);
});

ws.on('exception', (error) => {
  console.error('WebSocket exception:', error);
});

try {
  ws.subscribe(
    {
      topic: 'spot.balances',
    },
    WS_KEY_MAP.spotV4,
  );
} catch (error) {
  console.error('Balance subscription failed:', error);
}

try {
  ws.subscribe(
    {
      topic: 'spot.orders',
      payload: ['BTC_USDT'],
    },
    WS_KEY_MAP.spotV4,
  );
} catch (error) {
  console.error('Order subscription failed:', error);
}

try {
  ws.subscribe(
    {
      topic: 'spot.usertrades',
      payload: ['BTC_USDT'],
    },
    WS_KEY_MAP.spotV4,
  );
} catch (error) {
  console.error('Trade subscription failed:', error);
}

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

Use the authenticated event as a connection-level signal. Use private updates as private stream confirmation of account and order changes.

Other private Spot topics include spot.margin_balances and spot.cross_balances. Check each channel's payload rules before subscribing.

5. Subscribe to private Futures streams

Futures private channels use your Gate user ID. Contract-specific channels also include the contract. Retrieve the user ID once from the REST API, then use the payload required by each channel.

import { RestClient, WebsocketClient, WS_KEY_MAP } from 'gateio-api';

const rest = new RestClient({
  apiKey: process.env.GATE_API_KEY,
  apiSecret: process.env.GATE_API_SECRET,
});

const ws = new WebsocketClient({
  apiKey: process.env.GATE_API_KEY,
  apiSecret: process.env.GATE_API_SECRET,
});

ws.on('authenticated', ({ wsKey }) => {
  console.log('Authenticated:', wsKey);
});

ws.on('response', (response) => {
  console.log('Subscription response:', response);
});

ws.on('update', (update) => {
  console.log('Private Futures update:', update);
});

ws.on('exception', (error) => {
  console.error('WebSocket exception:', error);
});

async function main() {
  let account;

  try {
    account = await rest.getAccountDetail();
    console.log('Gate user ID:', account.user_id);
  } catch (error) {
    console.error('Account detail request failed:', error);
    return;
  }

  const userId = String(account.user_id);
  const payload = [userId, 'BTC_USDT'];
  const wsKey = WS_KEY_MAP.perpFuturesUSDTV4;

  try {
    ws.subscribe(
      {
        topic: 'futures.orders',
        payload,
      },
      wsKey,
    );
  } catch (error) {
    console.error('Order subscription failed:', error);
  }

  try {
    ws.subscribe(
      {
        topic: 'futures.usertrades',
        payload,
      },
      wsKey,
    );
  } catch (error) {
    console.error('Trade subscription failed:', error);
  }

  try {
    ws.subscribe(
      {
        topic: 'futures.balances',
        payload: [userId],
      },
      wsKey,
    );
  } catch (error) {
    console.error('Balance subscription failed:', error);
  }

  try {
    ws.subscribe(
      {
        topic: 'futures.positions',
        payload,
      },
      wsKey,
    );
  } catch (error) {
    console.error('Position subscription failed:', error);
  }
}

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

main();

Keep the wsKey aligned with the settlement currency and product. USDT perpetual Futures use WS_KEY_MAP.perpFuturesUSDTV4; BTC perpetual Futures use WS_KEY_MAP.perpFuturesBTCV4.

Gate also provides private Futures channels such as futures.autoorders. Use the payload documented for that specific channel.

6. Place and cancel a Spot order in TestNet

Gate TestNet uses separate credentials and API hosts. This example:

  1. Checks the explicit order opt-in.
  2. Loads current BTC_USDT trading rules and the order book.
  3. Calculates an amount that meets the pair's base and quote minimums.
  4. Places a post-only limit buy at the current best bid.
  5. Queries the order and cancels it if it remains open.

The order uses time_in_force: 'poc', Gate's post-only setting. A fast market can move before the request arrives, in which case Gate can reject or finish the order instead of leaving it on the book.

import { RestClient } from 'gateio-api';

const client = new RestClient({
  apiKey: process.env.GATE_TESTNET_API_KEY,
  apiSecret: process.env.GATE_TESTNET_API_SECRET,
  baseUrl: 'https://api-testnet.gateapi.io/api/v4',
});

async function main() {
  if (process.env.GATE_PLACE_TESTNET_ORDER !== 'true') {
    console.error('Set GATE_PLACE_TESTNET_ORDER=true to run this example.');
    return;
  }

  if (
    !process.env.GATE_TESTNET_API_KEY ||
    !process.env.GATE_TESTNET_API_SECRET
  ) {
    console.error('Set GATE_TESTNET_API_KEY and GATE_TESTNET_API_SECRET.');
    return;
  }

  let pair;

  try {
    pair = await client.getSpotCurrencyPair({
      currency_pair: 'BTC_USDT',
    });
  } catch (error) {
    console.error('Currency pair request failed:', error);
    return;
  }

  let orderBook;

  try {
    orderBook = await client.getSpotOrderBook({
      currency_pair: 'BTC_USDT',
      limit: 5,
    });
  } catch (error) {
    console.error('Order book request failed:', error);
    return;
  }

  if (pair.trade_status !== 'tradable' && pair.trade_status !== 'buyable') {
    console.error('BTC_USDT is not currently buyable.');
    return;
  }

  const price = orderBook.bids[0]?.[0];
  const amountPrecision = pair.amount_precision;
  const minBase = Number(pair.min_base_amount);
  const minQuote = Number(pair.min_quote_amount);
  const priceNumber = Number(price);

  if (
    !price ||
    amountPrecision === undefined ||
    !Number.isFinite(minBase) ||
    !Number.isFinite(minQuote) ||
    !Number.isFinite(priceNumber) ||
    priceNumber <= 0
  ) {
    console.error('Required market metadata is unavailable.');
    return;
  }

  const amountStep = 10 ** -amountPrecision;
  const minimumAmount = Math.max(minBase, minQuote / priceNumber);
  const amountNumber = (Math.ceil(minimumAmount / amountStep) + 1) * amountStep;
  const amount = amountNumber.toFixed(amountPrecision);

  if (amountNumber < minBase || amountNumber * priceNumber < minQuote) {
    console.error('Could not derive a valid TestNet order.');
    return;
  }

  const maxBase = Number(pair.max_base_amount);
  const maxQuote = Number(pair.max_quote_amount);

  if (
    (Number.isFinite(maxBase) && amountNumber > maxBase) ||
    (Number.isFinite(maxQuote) && amountNumber * priceNumber > maxQuote)
  ) {
    console.error('Derived amount exceeds the pair limits.');
    return;
  }

  const text = `t-siebly${Date.now().toString().slice(-12)}`;
  let placedOrder;

  try {
    placedOrder = await client.submitSpotOrder({
      currency_pair: 'BTC_USDT',
      account: 'spot',
      side: 'buy',
      type: 'limit',
      amount,
      price,
      time_in_force: 'poc',
      text,
    });
    console.log('Placed Spot order:', placedOrder);
  } catch (error) {
    console.error('Spot order placement failed:', error);
    return;
  }

  if (!placedOrder.id) {
    console.error('Gate did not return an order ID.');
    return;
  }

  let currentOrder;

  try {
    currentOrder = await client.getSpotOrder({
      order_id: placedOrder.id,
      currency_pair: 'BTC_USDT',
      account: 'spot',
    });
    console.log('Current Spot order:', currentOrder);
  } catch (error) {
    console.error('Spot order query failed:', error);
  }

  if (currentOrder && currentOrder.status !== 'open') {
    console.log('Order is no longer open:', currentOrder.status);
    return;
  }

  try {
    const cancelledOrder = await client.cancelSpotOrder({
      order_id: placedOrder.id,
      currency_pair: 'BTC_USDT',
      account: 'spot',
    });
    console.log('Cancelled Spot order:', cancelledOrder);
  } catch (error) {
    console.error('Spot order cancellation failed:', error);
  }
}

main();

The extra amount increment avoids falling just below the quote minimum after decimal rounding. Before using another pair, read its amount_precision, precision, minimums, maximums, and trade_status.

7. Place and cancel a USDT perpetual order in TestNet

A Futures size is a contract count. It is not an amount of BTC or USDT. This example reads the contract's minimum size and current price, and it only runs when the account is in one-way mode. It does not change account or position settings.

import { RestClient } from 'gateio-api';

const client = new RestClient({
  apiKey: process.env.GATE_TESTNET_API_KEY,
  apiSecret: process.env.GATE_TESTNET_API_SECRET,
  baseUrl: 'https://api-testnet.gateapi.io/api/v4',
});

async function main() {
  if (process.env.GATE_PLACE_TESTNET_ORDER !== 'true') {
    console.error('Set GATE_PLACE_TESTNET_ORDER=true to run this example.');
    return;
  }

  if (
    !process.env.GATE_TESTNET_API_KEY ||
    !process.env.GATE_TESTNET_API_SECRET
  ) {
    console.error('Set GATE_TESTNET_API_KEY and GATE_TESTNET_API_SECRET.');
    return;
  }

  let contract;

  try {
    contract = await client.getFuturesContract({
      settle: 'usdt',
      contract: 'BTC_USDT',
    });
  } catch (error) {
    console.error('Contract request failed:', error);
    return;
  }

  let orderBook;

  try {
    orderBook = await client.getFuturesOrderBook({
      settle: 'usdt',
      contract: 'BTC_USDT',
      limit: 5,
    });
  } catch (error) {
    console.error('Order book request failed:', error);
    return;
  }

  let account;

  try {
    account = await client.getFuturesAccount({
      settle: 'usdt',
    });
  } catch (error) {
    console.error('Futures account request failed:', error);
    return;
  }

  const oneWayMode = account.position_mode
    ? account.position_mode === 'single'
    : !account.in_dual_mode;

  if (!oneWayMode) {
    console.error('This example requires one-way Futures position mode.');
    return;
  }

  if (contract.status && contract.status !== 'trading') {
    console.error('BTC_USDT Futures is not currently trading.');
    return;
  }

  const price = orderBook.bids[0]?.p;
  const size = Number(contract.order_size_min);

  if (
    !price ||
    !Number.isFinite(Number(price)) ||
    Number(price) <= 0 ||
    !Number.isFinite(size) ||
    size <= 0 ||
    (contract.enable_decimal === false && !Number.isInteger(size))
  ) {
    console.error('Required contract metadata is unavailable.');
    return;
  }

  console.log('One contract represents:', contract.quanto_multiplier, 'BTC');
  console.log('Price increment:', contract.order_price_round);

  const text = `t-siebly${Date.now().toString().slice(-12)}`;
  let placedOrder;

  try {
    placedOrder = await client.submitFuturesOrder({
      settle: 'usdt',
      contract: 'BTC_USDT',
      size,
      price,
      tif: 'poc',
      text,
    });
    console.log('Placed Futures order:', placedOrder);
  } catch (error) {
    console.error('Futures order placement failed:', error);
    return;
  }

  if (placedOrder.id === undefined) {
    console.error('Gate did not return an order ID.');
    return;
  }

  const orderId = String(placedOrder.id);
  let currentOrder;

  try {
    currentOrder = await client.getFuturesOrder({
      settle: 'usdt',
      order_id: orderId,
    });
    console.log('Current Futures order:', currentOrder);
  } catch (error) {
    console.error('Futures order query failed:', error);
  }

  if (currentOrder && currentOrder.status !== 'open') {
    console.log('Order is no longer open:', currentOrder.status);
    return;
  }

  try {
    const cancelledOrder = await client.cancelFuturesOrder({
      settle: 'usdt',
      order_id: orderId,
    });
    console.log('Cancelled Futures order:', cancelledOrder);
  } catch (error) {
    console.error('Futures order cancellation failed:', error);
  }
}

main();

In one-way mode, a positive size buys and a negative size sells. Dual-side mode uses different position semantics, so handle it in account-specific code after reading the official Gate API v4 documentation.

The contract metadata supplies the size and price rules:

  • order_size_min: minimum contract count
  • order_price_round: permitted price increment
  • quanto_multiplier: underlying amount represented by one contract
  • enable_decimal: whether fractional contract sizes are supported

Do not derive a Futures order size from Spot precision rules.

8. Send a Spot order through the WebSocket API

WebsocketAPIClient turns Gate WebSocket API commands into promises. It connects and authenticates automatically on the first private command, adds a request ID, matches the response, and resolves the promise.

This TestNet example uses the REST API only to read current market rules and the order book. The order, query, and cancellation use the WebSocket API.

import { RestClient, WebsocketAPIClient, WS_KEY_MAP } from 'gateio-api';

const rest = new RestClient({
  baseUrl: 'https://api-testnet.gateapi.io/api/v4',
});

const wsApi = new WebsocketAPIClient({
  apiKey: process.env.GATE_TESTNET_API_KEY,
  apiSecret: process.env.GATE_TESTNET_API_SECRET,
  wsUrl: 'wss://ws-testnet.gate.com/v4/ws/spot',
});

async function main() {
  if (process.env.GATE_PLACE_TESTNET_ORDER !== 'true') {
    console.error('Set GATE_PLACE_TESTNET_ORDER=true to run this example.');
    return;
  }

  if (
    !process.env.GATE_TESTNET_API_KEY ||
    !process.env.GATE_TESTNET_API_SECRET
  ) {
    console.error('Set GATE_TESTNET_API_KEY and GATE_TESTNET_API_SECRET.');
    return;
  }

  let pair;

  try {
    pair = await rest.getSpotCurrencyPair({
      currency_pair: 'BTC_USDT',
    });
  } catch (error) {
    console.error('Currency pair request failed:', error);
    return;
  }

  let orderBook;

  try {
    orderBook = await rest.getSpotOrderBook({
      currency_pair: 'BTC_USDT',
      limit: 5,
    });
  } catch (error) {
    console.error('Order book request failed:', error);
    return;
  }

  if (pair.trade_status !== 'tradable' && pair.trade_status !== 'buyable') {
    console.error('BTC_USDT is not currently buyable.');
    return;
  }

  const price = orderBook.bids[0]?.[0];
  const amountPrecision = pair.amount_precision;
  const minBase = Number(pair.min_base_amount);
  const minQuote = Number(pair.min_quote_amount);
  const priceNumber = Number(price);

  if (
    !price ||
    amountPrecision === undefined ||
    !Number.isFinite(minBase) ||
    !Number.isFinite(minQuote) ||
    !Number.isFinite(priceNumber) ||
    priceNumber <= 0
  ) {
    console.error('Required market metadata is unavailable.');
    return;
  }

  const amountStep = 10 ** -amountPrecision;
  const minimumAmount = Math.max(minBase, minQuote / priceNumber);
  const amountNumber = (Math.ceil(minimumAmount / amountStep) + 1) * amountStep;
  const amount = amountNumber.toFixed(amountPrecision);

  if (amountNumber < minBase || amountNumber * priceNumber < minQuote) {
    console.error('Could not derive a valid TestNet order.');
    return;
  }

  const maxBase = Number(pair.max_base_amount);
  const maxQuote = Number(pair.max_quote_amount);

  if (
    (Number.isFinite(maxBase) && amountNumber > maxBase) ||
    (Number.isFinite(maxQuote) && amountNumber * priceNumber > maxQuote)
  ) {
    console.error('Derived amount exceeds the pair limits.');
    return;
  }

  const text = `t-siebly${Date.now().toString().slice(-12)}`;
  const wsKey = WS_KEY_MAP.spotV4;

  try {
    let placedResponse;

    try {
      placedResponse = await wsApi.submitNewSpotOrder(
        {
          currency_pair: 'BTC_USDT',
          account: 'spot',
          side: 'buy',
          type: 'limit',
          amount,
          price,
          time_in_force: 'poc',
          text,
        },
        wsKey,
      );
      console.log('Command status:', placedResponse.header.status);
      console.log('Placed Spot order:', placedResponse.data.result);
    } catch (error) {
      console.error('Spot WebSocket API placement failed:', error);
      return;
    }

    const orderId = placedResponse.data.result.id;
    let statusResponse;

    try {
      statusResponse = await wsApi.getSpotOrderStatus(
        {
          order_id: orderId,
          currency_pair: 'BTC_USDT',
          account: 'spot',
        },
        wsKey,
      );
      console.log('Current Spot order:', statusResponse.data.result);
    } catch (error) {
      console.error('Spot WebSocket API query failed:', error);
    }

    if (statusResponse && statusResponse.data.result.status !== 'open') {
      console.log(
        'Order is no longer open:',
        statusResponse.data.result.status,
      );
      return;
    }

    try {
      const cancelledResponse = await wsApi.cancelSpotOrder(
        {
          order_id: orderId,
          currency_pair: 'BTC_USDT',
          account: 'spot',
        },
        wsKey,
      );
      console.log('Cancelled Spot order:', cancelledResponse.data.result);
    } catch (error) {
      console.error('Spot WebSocket API cancellation failed:', error);
    }
  } finally {
    wsApi.getWSClient().closeAll();
  }
}

main();

9. Send a USDT perpetual order through the WebSocket API

Futures WebSocket API commands use a product-specific WebSocket key. Pass it explicitly so the contract and connection remain clear.

import { RestClient, WebsocketAPIClient, WS_KEY_MAP } from 'gateio-api';

const rest = new RestClient({
  apiKey: process.env.GATE_TESTNET_API_KEY,
  apiSecret: process.env.GATE_TESTNET_API_SECRET,
  baseUrl: 'https://api-testnet.gateapi.io/api/v4',
});

const wsApi = new WebsocketAPIClient({
  apiKey: process.env.GATE_TESTNET_API_KEY,
  apiSecret: process.env.GATE_TESTNET_API_SECRET,
  wsUrl: 'wss://ws-testnet.gate.com/v4/ws/futures/usdt',
});

async function main() {
  if (process.env.GATE_PLACE_TESTNET_ORDER !== 'true') {
    console.error('Set GATE_PLACE_TESTNET_ORDER=true to run this example.');
    return;
  }

  if (
    !process.env.GATE_TESTNET_API_KEY ||
    !process.env.GATE_TESTNET_API_SECRET
  ) {
    console.error('Set GATE_TESTNET_API_KEY and GATE_TESTNET_API_SECRET.');
    return;
  }

  let contract;

  try {
    contract = await rest.getFuturesContract({
      settle: 'usdt',
      contract: 'BTC_USDT',
    });
  } catch (error) {
    console.error('Contract request failed:', error);
    return;
  }

  let orderBook;

  try {
    orderBook = await rest.getFuturesOrderBook({
      settle: 'usdt',
      contract: 'BTC_USDT',
      limit: 5,
    });
  } catch (error) {
    console.error('Order book request failed:', error);
    return;
  }

  let account;

  try {
    account = await rest.getFuturesAccount({
      settle: 'usdt',
    });
  } catch (error) {
    console.error('Futures account request failed:', error);
    return;
  }

  const oneWayMode = account.position_mode
    ? account.position_mode === 'single'
    : !account.in_dual_mode;

  if (!oneWayMode) {
    console.error('This example requires one-way Futures position mode.');
    return;
  }

  if (contract.status && contract.status !== 'trading') {
    console.error('BTC_USDT Futures is not currently trading.');
    return;
  }

  const price = orderBook.bids[0]?.p;
  const size = Number(contract.order_size_min);

  if (
    !price ||
    !Number.isFinite(Number(price)) ||
    Number(price) <= 0 ||
    !Number.isFinite(size) ||
    size <= 0 ||
    (contract.enable_decimal === false && !Number.isInteger(size))
  ) {
    console.error('Required contract metadata is unavailable.');
    return;
  }

  const text = `t-siebly${Date.now().toString().slice(-12)}`;
  const wsKey = WS_KEY_MAP.perpFuturesUSDTV4;

  try {
    let placedResponse;

    try {
      placedResponse = await wsApi.submitNewFuturesOrder(
        {
          contract: 'BTC_USDT',
          size,
          price,
          tif: 'poc',
          text,
        },
        wsKey,
      );
      console.log('Command status:', placedResponse.header.status);
      console.log('Placed Futures order:', placedResponse.data.result);
    } catch (error) {
      console.error('Futures WebSocket API placement failed:', error);
      return;
    }

    const orderId = String(placedResponse.data.result.id);
    let statusResponse;

    try {
      statusResponse = await wsApi.getFuturesOrderStatus(
        {
          order_id: orderId,
        },
        wsKey,
      );
      console.log('Current Futures order:', statusResponse.data.result);
    } catch (error) {
      console.error('Futures WebSocket API query failed:', error);
    }

    if (statusResponse && statusResponse.data.result.status !== 'open') {
      console.log(
        'Order is no longer open:',
        statusResponse.data.result.status,
      );
      return;
    }

    try {
      const cancelledResponse = await wsApi.cancelFuturesOrder(
        {
          order_id: orderId,
        },
        wsKey,
      );
      console.log('Cancelled Futures order:', cancelledResponse.data.result);
    } catch (error) {
      console.error('Futures WebSocket API cancellation failed:', error);
    }
  } finally {
    wsApi.getWSClient().closeAll();
  }
}

main();

Successful commands have header.status === '200', and the endpoint result is in data.result. The SDK rejects a command promise when Gate returns a non-success status.

An accepted command is not proof that an order later filled, remained open, or was cancelled. Treat the response as pending confirmation, then confirm the final state through the private order stream or a REST API query.

Work with the Gate REST API

RestClient covers Gate's product groups through named methods. Public methods work without credentials. Private methods use the API key and secret passed to the client.

Understand response shapes

The client resolves to the endpoint's response body:

import { RestClient } from 'gateio-api';

const client = new RestClient();

async function main() {
  try {
    const result = await client.getSpotOrderBook({
      currency_pair: 'BTC_USDT',
    });
    console.log(result);
  } catch (error) {
    console.error('Order book request failed:', error);
  }
}

main();

There is no shared response envelope. Check the SDK endpoint map before deciding how to handle result.

MethodResponse shape
getServerTime()Object with server_time
getSpotCurrencyPair()Currency-pair object
getSpotTicker()Array of ticker objects
getSpotOrderBook()Object with tuple-array bids and asks
getSpotCandles()Array of candle tuples
getFuturesContract()Contract object
getFuturesTickers()Array of ticker objects
getFuturesOrderBook()Object with { p, s } bid and ask entries
getFuturesAccount()Account object
getFuturesPositions()Array of position objects

Prices and quantities are commonly returned as strings. Keep them as strings while passing them between Gate API methods. Convert them deliberately when doing calculations, and round the result back to the product's required precision.

Read account state

An authenticated integration usually needs these views:

StateSpot methodUSDT perpetual method
Balances or accountgetSpotAccounts()getFuturesAccount({ settle: 'usdt' })
PositionsNot applicablegetFuturesPositions({ settle: 'usdt' })
Open ordersgetSpotOpenOrders()getFuturesOrders({ settle: 'usdt', status: 'open' })
Recent ordersgetSpotOrders()getFuturesOrders({ settle: 'usdt', status: 'finished' })
TradesgetSpotTradingHistory()getFuturesTradingHistory()

These REST API views form a reliable account state baseline before stream updates are applied.

Explore other Gate product groups

The same RestClient also covers the following APIs. Start with read methods and check account eligibility, permissions, and product-specific rules before using any write method.

Product groupRepresentative SDK methods
Margin and cross margingetMarginAccounts(), getCrossMarginAccount(), getMarginUserAccounts()
Universal margin loansgetMarginUNILoans(), getMarginUNIMaxBorrow()
Unified AccountgetUnifiedAccountInfo(), getUnifiedAccountMode(), getUnifiedLoans()
DeliverygetAllDeliveryContracts(), getDeliveryAccount(), getDeliveryOrders()
OptionsgetOptionsContracts(), getOptionsAccount(), getOptionsOrders()
Wallet and transfersgetBalances(), submitTransfer(), getTransferStatus()
Multi-collateral loansgetMultiLoanOrders(), getMultiLoanSupportedCurrencies()
Flash SwapgetFlashSwapCurrencyPairs(), getFlashSwapOrders()
Earn and lendinggetEarnFixedTermProducts(), getEarnFixedTermLends()
Auto InvestgetAutoInvestCoins(), getAutoInvestPlans(), getAutoInvestOrders()
SubaccountsgetSubAccounts(), getSubBalance(), getSubFuturesBalances()
Rebates and partnersgetPartnerTransactionHistory(), getPartnerCommissionHistory()
OTCgetOTCBankList(), getOTCFiatOrderList()
P2P MerchantgetP2PMerchantUserInfo(), getP2PMerchantPendingTransactionList()
CrossExgetCrossExSymbols(), getCrossExAccounts(), getCrossExPositions()
AlphagetAlphaCurrencies(), getAlphaTickers(), getAlphaOrders()
TradFigetTradFiSymbols(), getTradFiAssets(), getTradFiOrders()

See the complete SDK endpoint map for every supported method, its authentication requirement, HTTP method, and Gate path.

Build with WebSocket streams

Use WebsocketClient for ongoing market and account updates. A subscription request has two independent parts:

  • topic and payload select the Gate channel and its data.
  • wsKey selects the product connection that carries the subscription.

One subscribe() call can contain several topics when they share the same wsKey. Use separate calls for topics on different product connections.

Choose the correct WebSocket key

Product connectionSDK key
SpotWS_KEY_MAP.spotV4
USDT perpetual FuturesWS_KEY_MAP.perpFuturesUSDTV4
BTC perpetual FuturesWS_KEY_MAP.perpFuturesBTCV4
USDT DeliveryWS_KEY_MAP.deliveryFuturesUSDTV4
BTC DeliveryWS_KEY_MAP.deliveryFuturesBTCV4
OptionsWS_KEY_MAP.optionsV4
AnnouncementsWS_KEY_MAP.announcementsV4

Do not infer the connection from a symbol alone. BTC_USDT can appear in more than one product family.

Useful public topics include:

ProductStarting topics
Spotspot.tickers, spot.trades, spot.order_book, spot.order_book_update, spot.candlesticks
Futuresfutures.tickers, futures.trades, futures.order_book, futures.order_book_update, futures.candlesticks
Optionsoptions.tickers, options.trades, options.order_book, options.ul_tickers

Treat responses and updates differently

The response event tells you whether Gate accepted a subscribe, unsubscribe, or request message. Check its error and result.

The update event carries channel data. Inspect channel, event, result, and the SDK-added wsKey before updating local state. A subscription response and a data update can have different result shapes.

Recover after a reconnect

The SDK reconnects and restores cached subscriptions. A stream gap can still hide balance, order, trade, or position changes. Reload the affected state from the REST API after reconnected.

This Spot example reloads balances, open orders, and recent trades in sequence:

import { RestClient, WebsocketClient, WS_KEY_MAP } from 'gateio-api';

const rest = new RestClient({
  apiKey: process.env.GATE_API_KEY,
  apiSecret: process.env.GATE_API_SECRET,
});

const ws = new WebsocketClient({
  apiKey: process.env.GATE_API_KEY,
  apiSecret: process.env.GATE_API_SECRET,
});

let recoveryRunning = false;
let spotState;

async function recoverSpotState() {
  if (recoveryRunning) {
    return;
  }

  recoveryRunning = true;

  try {
    let balances;

    try {
      balances = await rest.getSpotAccounts();
    } catch (error) {
      console.error('Balance recovery failed:', error);
      return;
    }

    let openOrders;

    try {
      openOrders = await rest.getSpotOpenOrders({
        account: 'spot',
      });
    } catch (error) {
      console.error('Open-order recovery failed:', error);
      return;
    }

    let trades;

    try {
      trades = await rest.getSpotTradingHistory({
        currency_pair: 'BTC_USDT',
        account: 'spot',
        limit: 100,
      });
    } catch (error) {
      console.error('Trade recovery failed:', error);
      return;
    }

    spotState = {
      balances,
      openOrders,
      trades,
      recoveredAt: Date.now(),
    };

    console.log('Recovered Spot state:', spotState);
  } finally {
    recoveryRunning = false;
  }
}

ws.on('reconnected', async ({ wsKey }) => {
  if (wsKey !== WS_KEY_MAP.spotV4) {
    return;
  }

  await recoverSpotState();
});

ws.on('update', (update) => {
  console.log('Private Spot update:', update);
});

ws.on('exception', (error) => {
  console.error('WebSocket exception:', error);
});

try {
  ws.subscribe(
    {
      topic: 'spot.balances',
    },
    WS_KEY_MAP.spotV4,
  );
} catch (error) {
  console.error('Balance subscription failed:', error);
}

try {
  ws.subscribe(
    {
      topic: 'spot.orders',
      payload: ['BTC_USDT'],
    },
    WS_KEY_MAP.spotV4,
  );
} catch (error) {
  console.error('Order subscription failed:', error);
}

try {
  ws.subscribe(
    {
      topic: 'spot.usertrades',
      payload: ['BTC_USDT'],
    },
    WS_KEY_MAP.spotV4,
  );
} catch (error) {
  console.error('Trade subscription failed:', error);
}

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

This is REST API hydration applied as scoped recovery. The example replaces spotState only after all three reads succeed. Reload only the state that may have changed on the affected connection, then reconcile it before trusting new updates.

For a wider recovery design, see Exchange State and Runtime Workflows.

Use the Gate WebSocket API

WebsocketAPIClient is for request and response commands over WebSocket. It is not the same as a subscription stream.

The client:

  1. Opens the selected WebSocket connection.
  2. Authenticates automatically.
  3. Adds a unique request ID.
  4. Sends the command.
  5. Matches Gate's response to the pending promise.
  6. Resolves with header, data.result, request_id, and wsKey.

Authentication happens automatically on the first command. connectWSAPI(wsKey) is available when an application wants to open and authenticate the connection early. Automatic WebSocket API reauthentication after a reconnect is enabled by default.

Spot methods include:

  • submitNewSpotOrder()
  • getSpotOrderStatus()
  • getSpotOrders()
  • updateSpotOrder()
  • cancelSpotOrder()
  • cancelSpotOrderById()
  • cancelSpotOrderForSymbol()

Futures methods include:

  • submitNewFuturesOrder()
  • submitNewFuturesBatchOrder()
  • getFuturesOrderStatus()
  • getFuturesOrders()
  • updateFuturesOrder()
  • cancelFuturesOrder()

Pass a Futures wsKey explicitly. This keeps the settlement connection visible:

import { WebsocketAPIClient, WS_KEY_MAP } from 'gateio-api';

const wsApi = new WebsocketAPIClient({
  apiKey: process.env.GATE_API_KEY,
  apiSecret: process.env.GATE_API_SECRET,
});

async function main() {
  try {
    const response = await wsApi.getFuturesOrders(
      {
        contract: 'BTC_USDT',
        status: 'open',
        limit: 20,
      },
      WS_KEY_MAP.perpFuturesUSDTV4,
    );

    console.log(response.header.status);
    console.log(response.data.result);
  } catch (error) {
    console.error('WebSocket API order request failed:', error);
  } finally {
    wsApi.getWSClient().closeAll();
  }
}

main();

The response confirms the command outcome. Use private order streams or a REST API query to confirm the resulting exchange state.

Use Gate TestNet

Gate TestNet is a separate environment with separate credentials. Use it for all order examples while learning the SDK.

InterfaceCurrent TestNet host
REST APIhttps://api-testnet.gateapi.io/api/v4
Spot WebSocketwss://ws-testnet.gate.com/v4/ws/spot
USDT perpetual WebSocketwss://ws-testnet.gate.com/v4/ws/futures/usdt

Set the REST API host with baseUrl and the WebSocket host with wsUrl:

import { RestClient, WebsocketClient } from 'gateio-api';

const rest = new RestClient({
  apiKey: process.env.GATE_TESTNET_API_KEY,
  apiSecret: process.env.GATE_TESTNET_API_SECRET,
  baseUrl: 'https://api-testnet.gateapi.io/api/v4',
});

const spotWs = new WebsocketClient({
  apiKey: process.env.GATE_TESTNET_API_KEY,
  apiSecret: process.env.GATE_TESTNET_API_SECRET,
  wsUrl: 'wss://ws-testnet.gate.com/v4/ws/spot',
});

Do not send live credentials to TestNet or TestNet credentials to live hosts. TestNet balances, liquidity, and available products can differ from live trading.

Fund the required Spot or Futures TestNet account with test assets before running an order example.

Connect to Global and regional REST API hosts

Use the API host associated with the Gate account and product you are integrating. Credentials and product availability are not interchangeable across account regions.

Environment or accountREST API base URLOfficial reference
Global livehttps://api.gateio.ws/api/v4Global Gate API v4
Global TestNethttps://api-testnet.gateapi.io/api/v4Global Gate API v4
Futures live alternativehttps://fx-api.gateio.ws/api/v4Global Gate API v4
Gate US livehttps://api.gate.us/api/v4Gate US API v4
Gate EU livehttps://api.gateeu.com/api/v4Gate EU API v4

Global live is the SDK default. Set baseUrl for another supported account host:

import { RestClient } from 'gateio-api';

const gateUs = new RestClient({
  apiKey: process.env.GATE_API_KEY,
  apiSecret: process.env.GATE_API_SECRET,
  baseUrl: 'https://api.gate.us/api/v4',
});

const gateEu = new RestClient({
  apiKey: process.env.GATE_API_KEY,
  apiSecret: process.env.GATE_API_SECRET,
  baseUrl: 'https://api.gateeu.com/api/v4',
});

Use only the client that matches the account. Regional APIs can expose different products and endpoint sets. Check the regional documentation before calling a method, and use the region's own credentials.

This guide does not assign regional WebSocket URLs. Use a regional WebSocket host only when the official documentation for that account region provides it.

Use a proxy with the REST API and WebSockets

gateio-api accepts standard Node.js agents. Configure the agent in both places when all Gate traffic must use the proxy:

  • Pass httpsAgent in the second RestClient argument and set proxy: false.
  • Pass the same agent as wsOptions.agent to WebsocketClient or WebsocketAPIClient.

See Using proxies with Siebly SDKs for deployment and troubleshooting guidance.

HTTP and HTTPS proxy

Install the proxy agent:

npm install https-proxy-agent

Set GATE_PROXY_URL to an http:// or https:// proxy URL. The example does not print the value because it may contain credentials.

import { HttpsProxyAgent } from 'https-proxy-agent';
import { RestClient, WebsocketClient, WS_KEY_MAP } from 'gateio-api';

async function main() {
  const proxyUrl = process.env.GATE_PROXY_URL;

  if (!proxyUrl) {
    console.error('Set GATE_PROXY_URL.');
    return;
  }

  const agent = new HttpsProxyAgent(proxyUrl);

  const rest = new RestClient(
    {},
    {
      httpsAgent: agent,
      proxy: false,
    },
  );

  const ws = new WebsocketClient({
    wsOptions: {
      agent,
    },
  });

  ws.on('open', ({ wsKey }) => {
    console.log('WebSocket connected through proxy:', wsKey);
  });

  ws.on('response', (response) => {
    console.log('Subscription response:', response);
  });

  ws.on('update', (update) => {
    console.log('Ticker update through proxy:', update);
    ws.closeAll();
  });

  ws.on('exception', () => {
    console.error('WebSocket proxy connection failed.');
  });

  try {
    const time = await rest.getServerTime();
    console.log('Server time through proxy:', time.server_time);
  } catch {
    console.error('REST API proxy request failed.');
  }

  try {
    ws.subscribe(
      {
        topic: 'spot.tickers',
        payload: ['BTC_USDT'],
      },
      WS_KEY_MAP.spotV4,
    );
  } catch {
    console.error('WebSocket proxy subscription failed.');
  }

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

main();

For an authenticated HTTP or HTTPS proxy, include the username and password in GATE_PROXY_URL. Percent-encode reserved characters in each credential component.

SOCKS5 proxy

Install the SOCKS agent:

npm install socks-proxy-agent

Set GATE_SOCKS_PROXY_URL to a socks5:// URL:

import { SocksProxyAgent } from 'socks-proxy-agent';
import { RestClient, WebsocketClient, WS_KEY_MAP } from 'gateio-api';

async function main() {
  const proxyUrl = process.env.GATE_SOCKS_PROXY_URL;

  if (!proxyUrl) {
    console.error('Set GATE_SOCKS_PROXY_URL.');
    return;
  }

  const agent = new SocksProxyAgent(proxyUrl);

  const rest = new RestClient(
    {},
    {
      httpsAgent: agent,
      proxy: false,
    },
  );

  const ws = new WebsocketClient({
    wsOptions: {
      agent,
    },
  });

  ws.on('open', ({ wsKey }) => {
    console.log('WebSocket connected through SOCKS5:', wsKey);
  });

  ws.on('response', (response) => {
    console.log('Subscription response:', response);
  });

  ws.on('update', (update) => {
    console.log('Ticker update through SOCKS5:', update);
    ws.closeAll();
  });

  ws.on('exception', () => {
    console.error('SOCKS5 WebSocket connection failed.');
  });

  try {
    const time = await rest.getServerTime();
    console.log('Server time through SOCKS5:', time.server_time);
  } catch {
    console.error('SOCKS5 REST API request failed.');
  }

  try {
    ws.subscribe(
      {
        topic: 'spot.tickers',
        payload: ['BTC_USDT'],
      },
      WS_KEY_MAP.spotV4,
    );
  } catch {
    console.error('SOCKS5 WebSocket subscription failed.');
  }

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

main();

Private REST API, stream, and WebSocket API traffic

Use the same agent and credentials for private REST API calls, private subscriptions, and WebSocket API connections:

import { HttpsProxyAgent } from 'https-proxy-agent';
import {
  RestClient,
  WebsocketAPIClient,
  WebsocketClient,
  WS_KEY_MAP,
} from 'gateio-api';

async function main() {
  const proxyUrl = process.env.GATE_PROXY_URL;

  if (!proxyUrl) {
    console.error('Set GATE_PROXY_URL.');
    return;
  }

  const agent = new HttpsProxyAgent(proxyUrl);

  const rest = new RestClient(
    {
      apiKey: process.env.GATE_API_KEY,
      apiSecret: process.env.GATE_API_SECRET,
    },
    {
      httpsAgent: agent,
      proxy: false,
    },
  );

  const streams = new WebsocketClient({
    apiKey: process.env.GATE_API_KEY,
    apiSecret: process.env.GATE_API_SECRET,
    wsOptions: {
      agent,
    },
  });

  const wsApi = new WebsocketAPIClient({
    apiKey: process.env.GATE_API_KEY,
    apiSecret: process.env.GATE_API_SECRET,
    wsOptions: {
      agent,
    },
  });

  streams.on('authenticated', ({ wsKey }) => {
    console.log('Private stream authenticated:', wsKey);
  });

  streams.on('update', (update) => {
    console.log('Private update through proxy:', update);
  });

  streams.on('exception', () => {
    console.error('Private stream proxy connection failed.');
  });

  try {
    const balances = await rest.getSpotAccounts();
    console.log('Spot balances through proxy:', balances);
  } catch {
    console.error('Private REST API proxy request failed.');
  }

  try {
    streams.subscribe(
      {
        topic: 'spot.balances',
      },
      WS_KEY_MAP.spotV4,
    );
  } catch {
    console.error('Private stream subscription failed.');
  }

  try {
    await wsApi.getWSClient().connectWSAPI(WS_KEY_MAP.spotV4);
    console.log('WebSocket API authenticated through proxy.');
  } catch {
    console.error('WebSocket API proxy connection failed.');
  }

  process.once('SIGINT', () => {
    streams.closeAll();
    wsApi.getWSClient().closeAll();
  });
}

main();

To combine a proxy with TestNet or a regional API, keep baseUrl or wsUrl in the first client argument and the agent in the same positions shown above.

The proxy's egress IP is the address Gate sees. Add that address to the API key whitelist when whitelisting is enabled. Use a stable proxy endpoint, monitor its latency, and never use a proxy to bypass account or regional restrictions.

A proxy changes the network path, not request signing, account eligibility, or regional product access. Test public REST API and WebSocket traffic first. Treat HTTP 407 responses, TLS failures, and repeated reconnects as network faults. The SDK does not rotate proxy endpoints.

Production checklist

Keep request time accurate

Gate private requests and WebSocket authentication depend on timestamp checks. The gap between request time and server time must not exceed 60 seconds.

Synchronize the host clock with NTP. Use getServerTime() for diagnosis, but fix the system clock rather than adding a permanent application offset for a drifting machine.

Respect rate limits

Rate limits vary by product and endpoint. Read the current Gate API rate-limit documentation and monitor rejection labels.

Retry temporary network failures and rate-limit responses with bounded exponential backoff and jitter. Do not blindly retry an order command after an ambiguous timeout. First reconcile by the order's text value or query current order state.

Use custom order IDs

Set a unique text value beginning with t- on each application-owned order. Store it with your local request record. This gives retries, restarts, stream updates, and REST API recovery a common identifier.

Apply product metadata

Before placing an order:

  • Check that the pair or contract is tradable.
  • Apply minimum and maximum amounts.
  • Round prices and quantities to the permitted increments.
  • Treat Futures size as a contract count.
  • Read quanto_multiplier before translating contract size into underlying exposure.
  • Keep one-way, dual-side, and split position modes explicit.

Refresh metadata regularly and when Gate rejects an order for a precision or size rule.

Confirm final state

A REST API response or WebSocket API response can arrive before later fills, cancellations, or amendments. Record the command response, then confirm final state through private streams or a REST API query.

After a reconnect, restore the affected exchange state before resuming state-dependent actions.

Scope credentials and network access

  • Use separate keys for live and TestNet.
  • Grant only the product permissions the process needs.
  • Use read-only keys for monitoring services.
  • Do not grant withdrawal permission.
  • Add IP whitelists when egress addresses are stable.
  • Keep secrets in a server-side secret manager or deployment environment.
  • Monitor proxy availability and egress IP changes.

Shut down connections

Call closeAll() on each WebSocket client when the process is ending. An unsubscribe request is unnecessary when the connection is about to close.

FAQ

Does gateio-api work with plain JavaScript?

Yes. Every example in this guide is plain JavaScript. The examples use ESM imports, so save them as .mjs files or set "type": "module" in package.json. The package also includes a CommonJS build and TypeScript declarations.

Which client should I use?

Use RestClient for public and private REST API calls, WebsocketClient for ongoing subscriptions, and WebsocketAPIClient for awaitable Spot and Futures commands over WebSocket.

Do public Gate API calls need credentials?

No. Public market data works without an API key. Account data, private streams, and order commands require credentials with the relevant product permission.

Why did a REST API call return an object when another returned an array?

Gate response bodies are endpoint-specific. The SDK returns the endpoint body directly. Check the endpoint map before reading the result.

Why does getSpotTicker() return an array for one pair?

That is Gate's endpoint response shape. Read the first entry after checking that the array is not empty.

How are Spot candles structured?

Spot candles are tuple arrays containing timestamp, quote volume, close, high, low, open, base volume, and a closed-window flag in that order.

Why does Gate use BTC_USDT?

Gate uses underscore-separated currency pairs and contract names. Pass the exact identifier returned by the relevant market or contract metadata endpoint.

What do account and settle change?

account selects the account used by a Spot order or query. settle selects the settlement currency and REST API path for Futures or Delivery requests.

What is the text order field?

text is Gate's custom order ID field. Use a unique value beginning with t- to reconcile requests, stream updates, and order queries.

Why am I not receiving WebSocket updates?

Check the topic payload, wsKey, API host, credentials, and permissions. Then inspect response, authenticated, and exception events. A successful subscription response confirms registration, but an update only arrives when the subscribed data changes.

Which WS_KEY_MAP value should I use?

Choose the key for the exact product connection. For example, Spot uses spotV4 and USDT perpetual Futures uses perpFuturesUSDTV4.

Why do private Futures topics use my Gate user ID?

It is part of the payload required by those channels. Retrieve it with getAccountDetail() instead of hardcoding it.

What is the difference between a stream and the WebSocket API?

Streams push subscribed market or account updates. The WebSocket API sends a command and resolves a promise with Gate's matching response.

Does a successful WebSocket API response mean the order filled?

No. It confirms the command result. Confirm later order and fill state through private streams or a REST API query.

Why was a post-only order rejected or immediately finished?

The best bid or ask can move between the order-book request and order placement. Gate will not let a post-only order take liquidity. Read the returned status and finish_as, then decide whether to refresh the book and submit a new order with a new text.

Why does a Futures order size look too small?

Futures size is the number of contracts. Use quanto_multiplier to find the underlying amount represented by one contract.

Can live credentials be used in TestNet?

No. TestNet and live trading use separate API keys and balances. Use the current TestNet baseUrl or wsUrl with TestNet credentials.

Can I use Gate US or Gate EU with the SDK?

Yes, for endpoints available to that account region. Set the documented regional baseUrl, use credentials from the same region, and check its official API documentation for product coverage.

Can REST API and WebSocket traffic use the same proxy?

Yes. Create one compatible agent, pass it as httpsAgent to RestClient, and pass it as wsOptions.agent to the WebSocket clients.

Does a proxy change signing or account eligibility?

No. The SDK signs the same requests through the proxy. The Gate account, API host, product permissions, and regional availability still apply.

What causes INVALID_SIGNATURE or REQUEST_EXPIRED?

Common causes are a drifting system clock, the wrong API secret, credentials from another environment, or an API request reaching the wrong host. Verify the clock, credential source, and API host without logging the secret.

Next steps

Subscribe on Substack

Complete the Substack form below to join our newsletter. Substack handles all subscriber data directly.