Skip to main content
Exchange

Developers

Exchange API

A REST API to read your account and trade programmatically. Requests are authenticated with an API key + secret using an HMAC-SHA256 signature — no password or session is ever sent. Create and manage keys on your API Keys page.

Base URL
https://your-exchange.example
Auth
HMAC-SHA256 signed
Response
{ code, message, data }

1. API keys & scopes

Create a key on the API Keys page. Each key has a secret (shown once — store it safely), an optional IP allowlist, a per-key rate limit, an optional expiry, and one or more scopes:

ScopeGrants
readRead account data — balances, open orders, history.
tradePlace, modify and cancel orders. Implies read.
withdrawReserved — disabled in this version. Programmatic withdrawals are not available.

Public market-data endpoints need no key. Scopes are enforced per endpoint and default-deny: a key can only reach endpoints its scope explicitly grants.

2. Authenticating a request

Every authenticated request sends three headers:

HeaderValue
X-API-KEYYour API key.
X-API-TIMESTAMPCurrent time in epoch milliseconds. Must be within 5 seconds of server time.
X-API-SIGNATUREHex HMAC-SHA256 of the canonical string (below), keyed by your API secret.

The canonical string

The signature is computed over five newline-joined parts:

METHOD\nPATH\nAPI_KEY\nTIMESTAMP\nSORTED_PARAMS
  • METHOD — the HTTP method, uppercase (e.g. GET, POST).
  • PATH — the service-relative path, i.e. the URL path with the service prefix removed. The prefix is the first path segment, /tx or /uc, and it is never part of the signed path:
    • call https://your-exchange.example/tx/spot/add → sign /spot/add
    • call https://your-exchange.example/uc/asset/spot-wallets → sign /asset/spot-wallets
    Signing the full path (e.g. /uc/asset/spot-wallets) is the most common cause of a signature error.
  • API_KEY — the same key sent in X-API-KEY.
  • TIMESTAMP — the same value sent in X-API-TIMESTAMP.
  • SORTED_PARAMS — all request parameters (query string + form body), sorted by key; for a repeated key its values are sorted too. Join each as key=value with &, using the raw (URL-decoded) values. Empty when there are no params.

3. Signing — code examples

Placing a spot order (POST /tx/spot/add, signed path /spot/add):

Node.js

const crypto = require('crypto');

const API_KEY = 'your-key';
const API_SECRET = 'your-secret';
const BASE = 'https://your-exchange.example';

const method = 'POST';
const publicPath = '/tx/spot/add';   // what you request
const signPath = '/spot/add';        // what you sign (service prefix removed)
const params = { symbol: 'BTCUSDT', direction: 'BUY', price: '60000', amount: '0.001' };
const timestamp = Date.now().toString();

// SORTED_PARAMS: keys sorted; values sorted per key; key=value joined by &
const sorted = Object.keys(params).sort()
  .map(k => `${k}=${params[k]}`).join('&');

const canonical = [method, signPath, API_KEY, timestamp, sorted].join('\n');
const signature = crypto.createHmac('sha256', API_SECRET).update(canonical).digest('hex');

fetch(BASE + publicPath, {
  method,
  headers: {
    'X-API-KEY': API_KEY,
    'X-API-TIMESTAMP': timestamp,
    'X-API-SIGNATURE': signature,
    'Content-Type': 'application/x-www-form-urlencoded',
  },
  body: new URLSearchParams(params),
}).then(r => r.json()).then(console.log);

Python

import time, hmac, hashlib, requests

API_KEY, API_SECRET = 'your-key', 'your-secret'
BASE = 'https://your-exchange.example'

method, public_path, sign_path = 'POST', '/tx/spot/add', '/spot/add'
params = {'symbol': 'BTCUSDT', 'direction': 'BUY', 'price': '60000', 'amount': '0.001'}
timestamp = str(int(time.time() * 1000))

sorted_params = '&'.join(f'{k}={params[k]}' for k in sorted(params))
canonical = '\n'.join([method, sign_path, API_KEY, timestamp, sorted_params])
signature = hmac.new(API_SECRET.encode(), canonical.encode(), hashlib.sha256).hexdigest()

r = requests.post(BASE + public_path, data=params, headers={
    'X-API-KEY': API_KEY,
    'X-API-TIMESTAMP': timestamp,
    'X-API-SIGNATURE': signature,
})
print(r.json())

For a GET endpoint (the spot reads), it's identical except the method is GET and the params go in the query string instead of the body — the canonical string uses GET and the same sorted params. Public market-data endpoints (no scope) are called without any of these headers.

Reading wallet balances (GET, no params)

GET /uc/asset/spot-wallets, signed path /asset/spot-wallets, needs a key with the read (or trade) scope. With no params, SORTED_PARAMS is empty, so the canonical string ends with a newline:

GET\n/asset/spot-wallets\n<API_KEY>\n<TIMESTAMP>\n
TS=$(date +%s%3N)
SIG=$(printf 'GET\n/asset/spot-wallets\n%s\n%s\n' "$API_KEY" "$TS" \
  | openssl dgst -sha256 -hmac "$API_SECRET" -hex | sed 's/^.* //')

curl "https://your-exchange.example/uc/asset/spot-wallets" \
  -H "X-API-KEY: $API_KEY" \
  -H "X-API-TIMESTAMP: $TS" \
  -H "X-API-SIGNATURE: $SIG"

To read one coin, add ?targetCoin=USDT to the URL and sign it as the last line: GET\n/asset/spot-wallets\n<API_KEY>\n<TIMESTAMP>\ntargetCoin=USDT.

Postman

Request: GET https://your-exchange.example/uc/asset/spot-wallets. Set collection variables apiKey and apiSecret, add the three headers below, and paste this as the request's Pre-request Script:

// Headers on the request:
//   X-API-KEY:       {{apiKey}}
//   X-API-TIMESTAMP: {{apiTs}}
//   X-API-SIGNATURE: {{apiSig}}
const key = pm.collectionVariables.get('apiKey');
const secret = pm.collectionVariables.get('apiSecret');
const ts = Date.now().toString();

// Sign the SERVICE-RELATIVE path: drop the first segment (/uc or /tx).
const segs = pm.request.url.path.filter(Boolean);
const signPath = '/' + segs.slice(1).join('/');

// The server signs DECODED values; Postman hands the script what you typed
// (possibly %-encoded), so decode first. Disabled params are not sent: skip them.
const dec = v => { try { return decodeURIComponent(String(v ?? '').replace(/\+/g, ' ')); } catch { return String(v ?? ''); } };

// Query params (and form-body params for POST), sorted by key, then by value.
const params = [];
pm.request.url.query.each(q => { if (!q.disabled) params.push([dec(q.key), dec(q.value)]); });
if (pm.request.body && pm.request.body.mode === 'urlencoded') {
  pm.request.body.urlencoded.each(p => { if (!p.disabled) params.push([p.key, p.value ?? '']); });
}
params.sort((a, b) => a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : a[1] < b[1] ? -1 : a[1] > b[1] ? 1 : 0);
const sorted = params.map(([k, v]) => k + '=' + v).join('&');

const canonical = [pm.request.method.toUpperCase(), signPath, key, ts, sorted].join('\n');
pm.collectionVariables.set('apiTs', ts);
pm.collectionVariables.set('apiSig', CryptoJS.HmacSHA256(canonical, secret).toString(CryptoJS.enc.Hex));

Send a fresh request each time: the timestamp must be within 5 seconds of server time and a signature is single-use.

4. Security & limits

  • Timestamp window. A request is rejected if its X-API-TIMESTAMP is more than 5 seconds from server time — sign each request fresh.
  • Replay protection. Each signature is single-use; re-sending an identical signed request is rejected.
  • IP allowlist. If you set an allowlist on a key, requests from other IPs are rejected.
  • Rate limit. Each key has a per-minute request limit (default 100). Exceeding it returns a rate-limit error.
  • Account status. Every request re-checks your account standing, so a restriction takes effect immediately even though keys outlive login sessions.
  • Transport. HTTPS only. Never expose your secret in client-side code.

5. Errors

Errors use the standard envelope: { "code": <n>, "message": "..." }. Common auth codes:

codeMeaning
4000Not authenticated — missing/invalid key, bad signature, or stale/replayed timestamp.
4030Forbidden — key lacks the required scope, IP not allowlisted, account restricted, or the endpoint isn’t accessible with a key.
4290Rate limit exceeded — slow down and retry.

6. Endpoints

Use the method shown per endpoint: the wallet-balance and spot read endpoints are GET (send params in the query string); everything else is POST (form-encoded body). Call the full path (/uc… or /tx…); sign the service-relative path (the leading /uc or /tx is stripped before checking — see §2). The signature always covers the method you send, so it must match the method column. The wallet-balance endpoints accept GET only, with a key.

Wallet balances

MethodCall · SignScopePurposeParams
GET/uc/asset/spot-wallets · /asset/spot-walletsreadSpot wallet balances (all coins, or one)targetCoin (optional)
GET/uc/asset/futures-wallets · /asset/futures-walletsreadFutures wallet balances—

Spot

MethodCall · SignScopePurposeParams
GET/tx/spot/balance · /spot/balancereadSpot balancescoin
GET/tx/spot/open-orders · /spot/open-ordersreadOpen spot orderssymbol, pageNo, pageSize
GET/tx/spot/history · /spot/historyreadSpot order historysymbol, pageNo, pageSize
POST/tx/spot/add · /spot/addtradePlace a spot ordersymbol, direction, type, amount (optional: price [required for limit orders, omit for market], stopPrice, callbackRate, timeInForce, takeProfit, stopLoss)
POST/tx/spot/cancel · /spot/canceltradeCancel a spot orderorderId
POST/tx/spot/modify · /spot/modifytradeModify a spot orderorderId, price, amount

Futures

MethodCall · SignScopePurposeParams
POST/tx/futures/positions · /futures/positionsreadOpen positions—
POST/tx/futures/current-entrust · /futures/current-entrustreadOpen (pending) futures orders—
POST/tx/futures/closed · /futures/closedreadClosed positionspageNo, pageSize
POST/tx/futures/history · /futures/historyreadFutures order historypageNo, pageSize
POST/tx/futures/close-history · /futures/close-historyreadClose / settlement historyorderId
POST/tx/futures-order/open · /futures-order/opentradePlace a futures order (sets leverage)contractCoinId, direction (0/1), type, leverage, volume, marginMode, entrustPrice, triggerPrice, reduceOnly, timeInForce, currency_name
POST/tx/futures-order/preview · /futures-order/previewtradePreview a futures order (margin / liq. price)contractCoinId, direction, leverage, volume, price
POST/tx/futures-close/close · /futures-close/closetradeClose a positionorderId
POST/tx/futures-close/partial · /futures-close/partialtradePartially close a positionorderId, volume
POST/tx/futures/cancel · /futures/canceltradeCancel a futures orderorderId
POST/tx/futures/cancel-entrust · /futures/cancel-entrusttradeCancel a pending futures orderentrustId
POST/tx/futures/cancel-all · /futures/cancel-alltradeCancel all orders for a contractcontractCoinId
POST/tx/futures/add-margin · /futures/add-margintradeAdd isolated marginorderId, amount
POST/tx/futures/reduce-margin · /futures/reduce-margintradeReduce isolated margin (moves the position nearer liquidation)orderId, amount
POST/tx/futures/reset-tpsl · /futures/reset-tpsltradeSet or clear take-profit / stop-lossorderId, tp, sl

Futures wallet

MethodCall · SignScopePurposeParams
POST/tx/futures/wallet/usdt · /futures/wallet/usdtreadUSDT-M futures wallet—
POST/tx/futures/wallet/coin · /futures/wallet/coinreadCoin-M futures wallet (per settle coin)coin
POST/tx/futures/wallet/list · /futures/wallet/listreadAll futures wallets—

Leverage is set when you open a position (the leverage param on /futures-order/open); to change it, close and reopen. Public market-data endpoints (depth, klines, mark price, funding rate, tickers) need no key. In the user service (/uc) a key can read the two wallet-balance endpoints above and nothing else: transfers, withdrawals, security settings and key management are refused for API keys. Per-endpoint request parameters are listed with each call; a trade key can only act on your own account (it can never move funds off-platform).

7. Withdrawals

Programmatic withdrawals are not available. The withdraw scope is reserved and inert — no endpoint accepts it. Withdrawals are done only from the website/app with their own confirmations.

8. Exchange listing (CoinMarketCap / CoinGecko)

Public, read-only market-data endpoints in the standard formats that listing aggregators poll. No API key or signature — they serve aggregate market data only (no balances, PII or internal ids). Rate-limited to 120 requests/min per IP. All figures are this exchange’s own on-exchange trades and order book (never mirrored external-venue volume), and cover enabled & visible pairs only. Pairs use the underscore form BTC_USDT.

EndpointSpecReturns
GET /cmc/summaryCoinMarketCap24h ticker for every pair (last price, base/quote volume, high/low)
GET /cmc/assetsCoinMarketCapListed coins with can_deposit / can_withdraw, min/max withdraw, maker/taker fee
GET /cmc/tickerCoinMarketCapPer-pair last price and 24h base/quote volume
GET /cmc/orderbook/{PAIR}CoinMarketCapReal order book (bids/asks) for the pair — empty for non-order-book (OTC) pairs
GET /cmc/trades/{PAIR}CoinMarketCapRecent own-exchange trades for the pair
GET /gecko/pairsCoinGeckoTradable pairs (ticker_id, base, target)
GET /gecko/tickersCoinGeckoPer-pair last price, volume and best bid/ask
GET /gecko/orderbook?ticker_id=CoinGeckoReal order book for the pair
GET /gecko/historical_trades?ticker_id=CoinGeckoRecent own-exchange trades for the pair

Base path is the market service context. These are for aggregator submission (CoinMarketCap, CoinGecko and compatible listing kits); a normal integration should use the keyed endpoints above.

Questions or issues with the API? Contact support from your account. Keep your API secret private — anyone with it can act on your account within the key’s scope.