◈   ⌘ api · Intermediate

OKX V5 API Authentication in Python: Complete Guide

Learn how to authenticate with OKX V5 API using Python, including HMAC signing, headers setup, and live trading requests with full code examples.

Uncle Solieditor · voc · 19.05.2026 ·views 8
◈   Contents
  1. → Getting Your OKX API Credentials
  2. → How OKX V5 Authentication Works
  3. → Building the Authentication Layer in Python
  4. → Making Live API Requests: Account and Market Data
  5. → Placing Orders via API
  6. → Error Handling and Rate Limits
  7. → Frequently Asked Questions
  8. → Conclusion

OKX V5 API is one of the most capable interfaces in the crypto space — and if you're building an algo trading system, a signal executor, or just want to pull live market data programmatically, getting authentication right is the foundation everything else stands on. The signing process trips up a lot of traders coming from simpler APIs like Binance's or Bybit's, but once you understand the pattern, it clicks fast.

Getting Your OKX API Credentials

Before writing a single line of Python, you need three things from OKX: an API key, a secret key, and a passphrase. Unlike Binance, which only uses key + secret, OKX V5 requires all three for any authenticated request. Log into your OKX account, go to Account → API, and create a new key. Assign only the permissions your bot actually needs — read-only for data, trade permission only if you're executing orders. Never enable withdrawal permissions on a trading key.

Store your API key, secret, and passphrase in environment variables or a secrets manager — never hardcode them in your script. A leaked key on GitHub means a drained account.

OKX also lets you whitelist IP addresses per API key. If you're running a bot on a VPS, add that server's IP. This single step eliminates most key-theft risk even if credentials somehow leak.

How OKX V5 Authentication Works

OKX V5 uses HMAC-SHA256 request signing. Every authenticated request must include four headers: OK-ACCESS-KEY (your API key), OK-ACCESS-SIGN (the signature), OK-ACCESS-TIMESTAMP (ISO 8601 UTC timestamp), and OK-ACCESS-PASSPHRASE. The signature is computed over a concatenated string: timestamp + HTTP method + request path + request body (empty string for GET requests). The secret key is used as the HMAC key, and the result is base64-encoded.

The timestamp must be within 30 seconds of OKX's server time — if your system clock drifts, you'll get 'Invalid timestamp' errors constantly. This is a common gotcha that Bybit and Gate.io handle more leniently, but OKX enforces it strictly.

Building the Authentication Layer in Python

Here's a clean, reusable auth module you can drop into any project. It handles signature generation, header construction, and works for both REST GET and POST requests.

import hmac
import hashlib
import base64
import os
from datetime import datetime, timezone

API_KEY = os.getenv('OKX_API_KEY')
SECRET_KEY = os.getenv('OKX_SECRET_KEY')
PASSPHRASE = os.getenv('OKX_PASSPHRASE')
BASE_URL = 'https://www.okx.com'


def get_timestamp() -> str:
    """Return ISO 8601 UTC timestamp OKX expects."""
    return datetime.now(timezone.utc).strftime('%Y-%m-%dT%H:%M:%S.%f')[:-3] + 'Z'


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'),
        hashlib.sha256
    )
    return base64.b64encode(mac.digest()).decode()


def build_headers(method: str, path: str, body: str = '') -> dict:
    ts = get_timestamp()
    return {
        'OK-ACCESS-KEY': API_KEY,
        'OK-ACCESS-SIGN': sign(ts, method, path, body),
        'OK-ACCESS-TIMESTAMP': ts,
        'OK-ACCESS-PASSPHRASE': PASSPHRASE,
        'Content-Type': 'application/json',
    }

With this module, every request you make simply calls build_headers() with the method and path. The signature is computed fresh each time — you can't reuse signatures across requests.

Making Live API Requests: Account and Market Data

Let's put authentication to work with two practical examples: fetching your account balance and pulling real-time order book data. These cover the two patterns you'll use constantly — authenticated private endpoints and public market data endpoints.

import requests
import json

# Authenticated: fetch account balance
def get_account_balance(currency: str = 'USDT') -> dict:
    path = '/api/v5/account/balance'
    params = f'?ccy={currency}'
    headers = build_headers('GET', path + params)
    
    resp = requests.get(BASE_URL + path + params, headers=headers, timeout=10)
    resp.raise_for_status()
    data = resp.json()
    
    if data['code'] != '0':
        raise RuntimeError(f"OKX API error {data['code']}: {data['msg']}")
    
    return data['data']


# Public: fetch order book (no auth needed)
def get_order_book(inst_id: str = 'BTC-USDT', depth: int = 20) -> dict:
    path = f'/api/v5/market/books?instId={inst_id}&sz={depth}'
    resp = requests.get(BASE_URL + path, timeout=10)
    resp.raise_for_status()
    return resp.json()['data'][0]


# Example usage
if __name__ == '__main__':
    balance = get_account_balance('USDT')
    print(f"USDT balance: {balance}")
    
    book = get_order_book('BTC-USDT')
    best_bid = book['bids'][0][0]
    best_ask = book['asks'][0][0]
    print(f"BTC-USDT — Bid: {best_bid}, Ask: {best_ask}")

Notice how public endpoints like order books don't need headers at all — only requests to private endpoints (account, orders, positions) require the signed headers. This split is consistent across most exchanges: Binance, Coinbase Advanced Trade, and OKX all follow this public/private pattern, but OKX's three-credential system makes it uniquely explicit about passphrase management.

Placing Orders via API

Reading data is safe. Placing orders is where bugs cost money. Here's a complete order placement function with the request body serialized correctly — OKX is strict about the body being an exact JSON string match between what you sign and what you send.

import json

def place_order(
    inst_id: str,
    side: str,          # 'buy' or 'sell'
    order_type: str,    # 'market' or 'limit'
    size: str,          # quantity as string
    price: str = '',    # required for limit orders
    td_mode: str = 'cash'  # 'cash', 'cross', 'isolated'
) -> dict:
    path = '/api/v5/trade/order'
    
    payload = {
        'instId': inst_id,
        'tdMode': td_mode,
        'side': side,
        'ordType': order_type,
        'sz': size,
    }
    if order_type == 'limit' and price:
        payload['px'] = price
    
    # Body must be serialized with separators to avoid whitespace differences
    body = json.dumps(payload, separators=(',', ':'))
    headers = build_headers('POST', path, body)
    
    resp = requests.post(
        BASE_URL + path,
        headers=headers,
        data=body,
        timeout=10
    )
    resp.raise_for_status()
    result = resp.json()
    
    if result['code'] != '0':
        raise RuntimeError(
            f"Order failed — code: {result['code']}, msg: {result['msg']}, "
            f"details: {result.get('data', [])}"
        )
    
    order_id = result['data'][0]['ordId']
    print(f"Order placed: {order_id}")
    return result['data'][0]


# Place a limit buy for 0.001 BTC at $60,000
try:
    order = place_order(
        inst_id='BTC-USDT',
        side='buy',
        order_type='limit',
        size='0.001',
        price='60000'
    )
except RuntimeError as e:
    print(f"Order error: {e}")
except requests.exceptions.RequestException as e:
    print(f"Network error: {e}")
json.dumps with separators=(',', ':') is critical — if your signed body has spaces but the sent body doesn't (or vice versa), you'll get signature mismatch errors that are painful to debug.

Error Handling and Rate Limits

OKX V5 returns structured error codes in the response body even when the HTTP status is 200 — you must check data['code'] on every response, not just the HTTP status. Code '0' means success; anything else is an error. Common codes you'll hit in production:

Common OKX V5 API Error Codes
Error CodeMeaningFix
50111Invalid signatureCheck SECRET_KEY encoding, body whitespace, timestamp format
50113Timestamp expiredSync system clock, ensure < 30s drift from OKX server time
50119Invalid passphraseVerify PASSPHRASE matches exactly — case-sensitive
51001Instrument not foundCheck instId format: 'BTC-USDT' not 'BTCUSDT'
51008Insufficient balanceCheck account balance and margin mode
429Rate limit exceededAdd exponential backoff — OKX limits by endpoint

OKX rate limits vary by endpoint: market data endpoints allow up to 40 requests/2 seconds per IP, while trading endpoints are stricter. Compare this to Bitget (20 req/2s for trading) or KuCoin (which uses a token bucket system). Build a simple rate limiter or use a library like ratelimit if you're calling OKX heavily. For production signal execution — especially if you're acting on signals from a platform like VoiceOfChain — a small sleep between order calls prevents limit errors from cascading.

VoiceOfChain provides real-time on-chain and order-flow signals that traders route to execution bots exactly like this. Having reliable API auth code means your bot can act on a whale accumulation signal within milliseconds of it firing — the authentication overhead in Python is under 1ms once keys are in memory.

Frequently Asked Questions

Why do I get 'Invalid signature' errors even though my code looks correct?
The most common cause is whitespace in the JSON body. Serialize with json.dumps(payload, separators=(',', ':')) and make sure the exact same string is both signed and sent. Also double-check that your SECRET_KEY has no trailing spaces or newline characters from environment variable loading.
Does OKX V5 API work for futures and spot trading the same way?
Authentication is identical across spot, futures, and options — the same headers and signing process work everywhere. The difference is in the instId format and tdMode: spot uses 'cash', cross-margin uses 'cross', and isolated margin uses 'isolated'. Perpetual futures instruments look like 'BTC-USDT-SWAP'.
How do I test my OKX API setup without risking real funds?
OKX provides a paper trading environment at https://www.okx.com/paper-trading. Create a separate set of API keys there — they work identically to live keys but against simulated balances. The base URL changes to the demo endpoint; everything else in your auth code stays the same.
What's the difference between OKX V5 and V3 API authentication?
V5 replaced V3 and V4 in 2021. The main differences: V5 uses ISO 8601 timestamps instead of Unix epoch, the passphrase is now mandatory (it was optional in V3), and the endpoint structure is reorganized. If you find old tutorials using Unix timestamps or missing the passphrase header, they're using the deprecated V3 format.
Can I use the same API key for WebSocket and REST?
Yes, OKX API keys work for both REST and WebSocket connections. For WebSocket authentication, you send a login op message with the same key, timestamp, signature, and passphrase — the signing method is identical to REST. After successful login, all private channel subscriptions use that authenticated session.
How do I handle OKX API rate limits in a high-frequency bot?
Track your request count per endpoint per 2-second window. When you hit 80% of the limit, insert a short sleep. Use exponential backoff on 429 responses: start with 1 second, double on each retry, cap at 30 seconds. For very high-frequency needs, consider using OKX WebSocket streams instead of polling REST endpoints — they push data without counting against REST rate limits.

Conclusion

OKX V5 API authentication in Python comes down to three moving parts: the three credentials (key, secret, passphrase), the HMAC-SHA256 signing of timestamp + method + path + body, and getting the timestamp fresh on every request. The auth module built here is production-ready — drop it into any project, load credentials from environment variables, and you're making authenticated calls within minutes. From there, whether you're pulling balance data, executing orders based on signals from VoiceOfChain, or building a full algo trading system, the authentication layer stays exactly the same. Get it solid once, and everything built on top of it is reliable.

◈   more on this topic
◉ basics Mastering the ccxt library documentation for crypto traders ⌂ exchanges Mastering the Binance CCXT Library for Crypto Traders ⌬ bots Best Crypto Trading Bots 2025: Profitable AI-Powered Strategies