OKX Spot Exchange API Documentation: A Trader's Guide
Master OKX spot exchange API documentation with real code examples, auth setup, and practical tips for automating crypto trading strategies.
Master OKX spot exchange API documentation with real code examples, auth setup, and practical tips for automating crypto trading strategies.
OKX runs one of the deepest spot order books in crypto, and their API is the gateway to everything — real-time prices, order placement, account balances, and historical candles. If you're moving beyond manual clicking and building a trading bot or analytics system, understanding the OKX spot exchange API documentation is non-negotiable. This guide cuts through the official docs and shows you exactly how to authenticate, pull market data, and place orders — with working code you can run today.
OKX offers two primary interfaces: a REST API for request-response operations and a WebSocket API for real-time streaming. For spot trading automation, you'll use both. REST is your workhorse for placing and canceling orders, querying account state, and fetching historical OHLCV data. WebSocket handles the stuff that needs to be instant — live order book updates, trade streams, and your own order fills.
The base REST URL is https://www.okx.com and all spot market endpoints sit under /api/v5/. Compare this to Binance's /api/v3/ structure or Bybit's /v5/ — the patterns are similar enough that if you've worked with one, OKX will feel familiar. The key difference is OKX's unified account model, which means your spot and margin positions share the same account namespace.
Before writing a single line of code, create your API key in the OKX web interface under Account > API. You'll get three credentials: API Key, Secret Key, and Passphrase. The passphrase is OKX-specific — Binance and Coinbase don't use one — so don't skip it. Store all three in environment variables, never hardcoded in your scripts.
OKX uses HMAC-SHA256 signatures for authenticated requests. The signature is built from a concatenation of timestamp, HTTP method, request path, and body. Here's a complete authentication setup in Python:
import hmac
import hashlib
import base64
import time
import os
import requests
API_KEY = os.environ['OKX_API_KEY']
SECRET_KEY = os.environ['OKX_SECRET_KEY']
PASSPHRASE = os.environ['OKX_PASSPHRASE']
BASE_URL = 'https://www.okx.com'
def sign(timestamp: str, method: str, path: str, body: str = '') -> str:
message = timestamp + method.upper() + path + body
mac = hmac.new(
SECRET_KEY.encode('utf-8'),
message.encode('utf-8'),
digestmod=hashlib.sha256
)
return base64.b64encode(mac.digest()).decode()
def get_headers(method: str, path: str, body: str = '') -> dict:
timestamp = str(time.time())
return {
'OK-ACCESS-KEY': API_KEY,
'OK-ACCESS-SIGN': sign(timestamp, method, path, body),
'OK-ACCESS-TIMESTAMP': timestamp,
'OK-ACCESS-PASSPHRASE': PASSPHRASE,
'Content-Type': 'application/json'
}
# Test: fetch account balance
path = '/api/v5/account/balance'
response = requests.get(
BASE_URL + path,
headers=get_headers('GET', path)
)
print(response.json())
Always use IP whitelisting when creating OKX API keys for production bots. Without it, a leaked key can drain your account from anywhere in the world. Set withdraw permissions to OFF unless your bot specifically needs it.
Public market data endpoints require no authentication. This is where you pull ticker prices, order book depth, and historical candles. OKX spot instruments follow the format BTC-USDT, ETH-USDT — hyphenated pairs, always uppercase. This differs from Binance's BTCUSDT format, so keep that in mind if you're porting code between exchanges.
import requests
BASE_URL = 'https://www.okx.com'
def get_ticker(inst_id: str) -> dict:
"""Fetch best bid/ask and last price for a spot pair."""
url = f'{BASE_URL}/api/v5/market/ticker'
resp = requests.get(url, params={'instId': inst_id})
resp.raise_for_status()
data = resp.json()
if data['code'] != '0':
raise ValueError(f"OKX error: {data['msg']}")
return data['data'][0]
def get_candles(inst_id: str, bar: str = '1H', limit: int = 100) -> list:
"""Fetch OHLCV candles. bar options: 1m, 5m, 15m, 1H, 4H, 1D"""
url = f'{BASE_URL}/api/v5/market/candles'
resp = requests.get(url, params={
'instId': inst_id,
'bar': bar,
'limit': limit
})
resp.raise_for_status()
data = resp.json()
# Each candle: [ts, open, high, low, close, vol, volCcy, volCcyQuote, confirm]
return data['data']
# Usage
ticker = get_ticker('BTC-USDT')
print(f"BTC last price: {ticker['last']} | Bid: {ticker['bidPx']} | Ask: {ticker['askPx']}")
candles = get_candles('ETH-USDT', bar='1H', limit=24)
print(f"Fetched {len(candles)} hourly candles for ETH-USDT")
print(f"Latest close: {candles[0][4]}")
One thing that trips people up: OKX returns candles in reverse chronological order by default — newest first. If you're feeding data into a library like pandas-ta or passing it to a signal engine like VoiceOfChain for backtesting, reverse the list first. The volume columns are also split: vol is base asset volume, volCcyQuote is the USDT-denominated volume most people actually want for liquidity assessment.
Order placement is where authentication matters. OKX's POST /api/v5/trade/order endpoint handles all order types: limit, market, post-only, IOC, and FOK. For spot trading, set tdMode to cash. If you're on a unified account and want cross-margin behavior, use cross instead — but cash is the right default for pure spot automation.
import json
import requests
# Assumes get_headers() from the auth setup above
def place_limit_order(
inst_id: str,
side: str, # 'buy' or 'sell'
price: str,
size: str, # quantity in base currency
client_oid: str = ''
) -> dict:
path = '/api/v5/trade/order'
body = {
'instId': inst_id,
'tdMode': 'cash',
'side': side,
'ordType': 'limit',
'px': price,
'sz': size,
}
if client_oid:
body['clOrdId'] = client_oid # your own reference ID
body_str = json.dumps(body)
response = requests.post(
BASE_URL + path,
headers=get_headers('POST', path, body_str),
data=body_str
)
result = response.json()
if result['code'] != '0':
raise ValueError(f"Order failed: {result['data'][0]['sMsg']}")
return result['data'][0]
def cancel_order(inst_id: str, ord_id: str) -> dict:
path = '/api/v5/trade/cancel-order'
body = json.dumps({'instId': inst_id, 'ordId': ord_id})
response = requests.post(
BASE_URL + path,
headers=get_headers('POST', path, body),
data=body
)
return response.json()
# Place a limit buy for 0.01 BTC at $60,000
order = place_limit_order(
inst_id='BTC-USDT',
side='buy',
price='60000',
size='0.01',
client_oid='my_bot_order_001'
)
print(f"Order placed: {order['ordId']}")
# Cancel it
cancel_result = cancel_order('BTC-USDT', order['ordId'])
print(f"Cancel status: {cancel_result['data'][0]['sCode']}")
Always pass a clOrdId (client order ID) when placing orders. If your network request times out mid-flight, you can query /api/v5/trade/order?clOrdId=your_id to check if the order landed — without risking a duplicate submission.
Polling REST endpoints for price updates is wasteful and slow. For any strategy that reacts to market moves — whether you're running a market maker on OKX, watching signals from VoiceOfChain, or monitoring a Bybit position hedge — WebSocket is the right tool. OKX's WebSocket API uses a subscribe/unsubscribe message pattern over a persistent connection.
import asyncio
import json
import websockets
async def stream_orderbook(inst_id: str):
uri = 'wss://ws.okx.com:8443/ws/v5/public'
subscribe_msg = {
'op': 'subscribe',
'args': [{
'channel': 'books5', # top 5 bids/asks
'instId': inst_id
}]
}
async with websockets.connect(uri, ping_interval=20) as ws:
await ws.send(json.dumps(subscribe_msg))
print(f'Subscribed to {inst_id} order book')
async for raw_msg in ws:
msg = json.loads(raw_msg)
if msg.get('event') == 'subscribe':
continue # confirmation, skip
data = msg.get('data', [])
if not data:
continue
book = data[0]
best_bid = book['bids'][0][0] if book['bids'] else 'N/A'
best_ask = book['asks'][0][0] if book['asks'] else 'N/A'
print(f"{inst_id} | Bid: {best_bid} | Ask: {best_ask}")
# Run it
asyncio.run(stream_orderbook('BTC-USDT'))
For private WebSocket channels (order fills, position updates), you need to send an authentication message immediately after connecting — before subscribing to any private channel. The auth message uses the same HMAC-SHA256 signature scheme as REST, with the path set to /users/self/verify and an empty body. OKX's official documentation covers this in the WebSocket Authentication section, and it's worth reading carefully because the timestamp format must be a Unix timestamp in seconds as a string, not milliseconds.
| Channel | Data | Update Frequency |
|---|---|---|
| tickers | Last price, 24h vol, bid/ask | 100ms |
| books5 | Top 5 bids and asks | 100ms |
| books | Full order book (400 levels) | On change |
| trades | Individual trade executions | Real-time |
| candle1m | 1-minute OHLCV candles | 500ms |
The OKX spot exchange API documentation is genuinely well-structured once you know where to look — the authentication pattern is consistent across all endpoints, the error codes are descriptive, and the WebSocket channels cover everything you'd need for a production system. Start with the public market data endpoints to validate your setup, add authentication for account queries, then layer in order management once you're confident in the signature logic.
For traders who want to combine algorithmic execution with intelligent signal generation, pairing your OKX API bot with VoiceOfChain gives you real-time on-chain signals — whale movements, exchange inflows, smart money positioning — that you can act on programmatically. The signal triggers from VoiceOfChain, your bot executes on OKX within milliseconds. That's the setup that closes the gap between spotting an opportunity and actually capturing it. Whether you're building on OKX, Bitget, Gate.io, or KuCoin, the principle is the same: faster, more informed execution wins.