◈   ⌘ api · Intermediate

OKX API Authentication Headers: Complete Setup Guide

Master OKX API authentication headers with step-by-step code examples. Learn to sign requests, handle errors, and build reliable trading bots.

Uncle Solieditor · voc · 05.05.2026 ·views 18
◈   Contents
  1. → Understanding the Four Required Headers
  2. → Building the Signature: Step-by-Step
  3. → Making Authenticated GET and POST Requests
  4. → Handling Common Authentication Errors
  5. → Integrating OKX API With Trading Signal Platforms
  6. → Frequently Asked Questions
  7. → Conclusion

If you've ever tried to hit the OKX API and got back a 401 or a cryptic 'Invalid Sign' error, you already know that authentication is where most developers get stuck. OKX uses a four-header authentication scheme that's stricter than what you'll find on Binance or Coinbase — every request needs a precise HMAC-SHA256 signature built from the timestamp, method, path, and body. Get one character wrong and the whole thing fails silently. This guide walks through exactly how to construct those headers, sign requests correctly, and avoid the common traps that waste hours of debugging time.

Understanding the Four Required Headers

OKX requires four specific headers on every authenticated request. These are not optional — missing any one of them will result in an authentication failure regardless of how correct the others are.

OKX Required Authentication Headers
HeaderValueExample
OK-ACCESS-KEYYour API keyabc123def456...
OK-ACCESS-SIGNHMAC-SHA256 signature (base64)computed per request
OK-ACCESS-TIMESTAMPISO 8601 UTC timestamp2024-01-15T10:30:00.000Z
OK-ACCESS-PASSPHRASEPassphrase set during key creationmySecurePassphrase

Unlike Bybit, which accepts Unix epoch integers for timestamps, OKX wants ISO 8601 format with milliseconds. And unlike KuCoin's passphrase which is hashed before sending, OKX sends your passphrase raw — which means it needs to be stored securely in environment variables, never hardcoded.

Never hardcode API credentials in your source code. Use environment variables or a secrets manager. OKX keys with withdrawal permissions are especially sensitive — a leaked key can drain a wallet within seconds.

Building the Signature: Step-by-Step

The signature is the most error-prone part of OKX authentication. The formula is straightforward but the details matter: you concatenate the timestamp, HTTP method (uppercase), request path including query string, and the request body, then sign that string with your secret key using HMAC-SHA256, and base64-encode the result.

The prehash string follows this exact format: `{timestamp}{method}{requestPath}{body}`. For GET requests with no body, the body portion is an empty string — not null, not a space, just nothing. For POST requests, the body is the raw JSON string exactly as it will be sent. Any difference between the body used for signing and the body actually sent will cause signature verification to fail.

import hmac
import hashlib
import base64
import datetime
import os

def get_timestamp():
    """Returns ISO 8601 timestamp with milliseconds in UTC."""
    return datetime.datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%S.') + \
           str(datetime.datetime.utcnow().microsecond // 1000).zfill(3) + 'Z'

def sign_request(secret_key: str, timestamp: str, method: str, 
                 request_path: str, body: str = '') -> str:
    """
    Build HMAC-SHA256 signature for OKX API request.
    
    Args:
        secret_key: Your OKX API secret key
        timestamp: ISO 8601 UTC timestamp (from get_timestamp())
        method: HTTP method uppercase ('GET', 'POST', 'DELETE')
        request_path: Full path including query params e.g. '/api/v5/account/balance'
        body: Raw JSON string for POST requests, empty string for GET
    Returns:
        base64-encoded HMAC-SHA256 signature
    """
    prehash = timestamp + method.upper() + request_path + body
    mac = hmac.new(
        secret_key.encode('utf-8'),
        prehash.encode('utf-8'),
        hashlib.sha256
    )
    return base64.b64encode(mac.digest()).decode('utf-8')

# Example usage
API_KEY = os.environ.get('OKX_API_KEY')
SECRET_KEY = os.environ.get('OKX_SECRET_KEY')
PASSPHRASE = os.environ.get('OKX_PASSPHRASE')

timestamp = get_timestamp()
method = 'GET'
path = '/api/v5/account/balance'

signature = sign_request(SECRET_KEY, timestamp, method, path)

headers = {
    'OK-ACCESS-KEY': API_KEY,
    'OK-ACCESS-SIGN': signature,
    'OK-ACCESS-TIMESTAMP': timestamp,
    'OK-ACCESS-PASSPHRASE': PASSPHRASE,
    'Content-Type': 'application/json'
}

print('Headers ready:', list(headers.keys()))

Making Authenticated GET and POST Requests

With the signature function in place, making actual API calls is straightforward. The key difference between GET and POST is how query parameters are handled in the signature — for GET requests they must be included in the path string, not as a separate dict passed to requests.

import requests
import json

BASE_URL = 'https://www.okx.com'

def okx_get(path: str, params: dict = None) -> dict:
    """
    Authenticated GET request to OKX API.
    Query params must be appended to path for correct signature.
    """
    if params:
        query_string = '?' + '&'.join(f'{k}={v}' for k, v in params.items())
        signed_path = path + query_string
    else:
        signed_path = path
    
    ts = get_timestamp()
    sig = sign_request(SECRET_KEY, ts, 'GET', signed_path)
    
    headers = {
        'OK-ACCESS-KEY': API_KEY,
        'OK-ACCESS-SIGN': sig,
        'OK-ACCESS-TIMESTAMP': ts,
        'OK-ACCESS-PASSPHRASE': PASSPHRASE,
        'Content-Type': 'application/json'
    }
    
    resp = requests.get(BASE_URL + signed_path, headers=headers, timeout=10)
    resp.raise_for_status()
    return resp.json()

def okx_post(path: str, payload: dict) -> dict:
    """
    Authenticated POST request to OKX API.
    Body must be serialized to string before signing.
    """
    body = json.dumps(payload)  # must use exact same string for signing and sending
    ts = get_timestamp()
    sig = sign_request(SECRET_KEY, ts, 'POST', path, body)
    
    headers = {
        'OK-ACCESS-KEY': API_KEY,
        'OK-ACCESS-SIGN': sig,
        'OK-ACCESS-TIMESTAMP': ts,
        'OK-ACCESS-PASSPHRASE': PASSPHRASE,
        'Content-Type': 'application/json'
    }
    
    resp = requests.post(BASE_URL + path, headers=headers, data=body, timeout=10)
    resp.raise_for_status()
    return resp.json()

# Fetch account balance
try:
    balance = okx_get('/api/v5/account/balance')
    print('Balance data:', json.dumps(balance['data'][0]['details'][:2], indent=2))
except requests.exceptions.HTTPError as e:
    print(f'HTTP error: {e.response.status_code} — {e.response.text}')
except Exception as e:
    print(f'Request failed: {e}')

# Place a limit order
order_payload = {
    'instId': 'BTC-USDT',
    'tdMode': 'cash',
    'side': 'buy',
    'ordType': 'limit',
    'px': '40000',
    'sz': '0.001'
}
try:
    result = okx_post('/api/v5/trade/order', order_payload)
    print('Order result:', result)
except Exception as e:
    print(f'Order failed: {e}')

Notice that `data=body` is used instead of `json=payload` in the POST call. This is intentional — using `json=` would let requests re-serialize the dict, potentially changing key ordering or whitespace and breaking the signature. Always sign the exact bytes you're sending.

Handling Common Authentication Errors

OKX returns error codes in the response body even on 200 status responses. A successful request returns `'code': '0'`, while errors return specific codes that tell you exactly what went wrong. This is different from how Binance handles errors — where HTTP status codes carry more weight.

import ntplib
from time import ctime

OKX_ERROR_MESSAGES = {
    '50111': 'Invalid API key — check OK-ACCESS-KEY value',
    '50112': 'Timestamp invalid — sync system clock (skew > 30s)',
    '50113': 'Signature invalid — check body serialization and path format',
    '50114': 'Wrong passphrase for this API key',
    '50102': 'Request expired — regenerate timestamp closer to sending',
}

def handle_okx_response(response: dict) -> dict:
    """
    Parse OKX response and raise descriptive errors on failure.
    OKX returns code '0' on success, other codes on error.
    """
    code = response.get('code', '0')
    if code != '0':
        msg = response.get('msg', 'Unknown error')
        friendly = OKX_ERROR_MESSAGES.get(code, msg)
        raise ValueError(f'OKX API Error {code}: {friendly}')
    return response.get('data', [])

def check_clock_sync() -> float:
    """Returns clock offset in seconds vs NTP. OKX allows max 30s skew."""
    try:
        c = ntplib.NTPClient()
        response = c.request('pool.ntp.org', version=3)
        offset = response.offset
        if abs(offset) > 25:
            print(f'WARNING: Clock offset {offset:.1f}s — approaching OKX 30s limit')
        return offset
    except Exception:
        print('Could not check NTP — ensure system time is synced')
        return 0.0

# Usage in your trading loop
try:
    raw = okx_get('/api/v5/market/ticker', {'instId': 'ETH-USDT'})
    data = handle_okx_response(raw)
    ticker = data[0]
    print(f"ETH/USDT last price: {ticker['last']} | 24h vol: {ticker['vol24h']}")
except ValueError as e:
    print(f'API logic error: {e}')
except requests.exceptions.Timeout:
    print('Request timed out — OKX may be experiencing issues')
except requests.exceptions.ConnectionError:
    print('Network error — check connectivity')
The most common cause of error 50113 (invalid signature) on POST requests is using json= instead of data= in requests.post(), or building the body dict differently than what was signed. Always sign the exact serialized string you send.

Integrating OKX API With Trading Signal Platforms

Raw API access becomes far more powerful when combined with real-time signal data. Platforms like VoiceOfChain provide live trading signals across major pairs — BTC, ETH, SOL, and others — that you can consume programmatically and act on via exchange APIs. The typical pattern is: receive signal from VoiceOfChain → validate against current OKX order book via API → execute if conditions match → manage position via authenticated endpoints.

Compared to Binance's API, OKX has more granular order types available at the REST level — including algo orders like iceberg and TWAP directly through the `/api/v5/trade/order-algo` endpoint. Bybit has similar depth, but OKX's unified account model means one authenticated session covers spot, margin, futures, and options simultaneously. For traders running multi-strategy bots, this reduces the complexity of managing separate credential sets per product type.

When building a signal-to-execution pipeline, structure your authentication layer as a thin wrapper that all strategy modules share. This way credential rotation — which should happen every 90 days — only requires updating one place. Gate.io and Bitget follow similar four-header patterns, so the wrapper architecture translates across exchanges with minimal changes to the signing logic.

Frequently Asked Questions

Why do I keep getting error 50113 (invalid signature) even though my key and passphrase are correct?
The most common cause is a mismatch between the body string used for signing and the body actually sent in the request. Never use json= in requests.post() — serialize to a string first with json.dumps() and pass that same string to both the sign function and the data= parameter. Also double-check that your request path includes query parameters if any.
Does OKX API work with demo/paper trading accounts?
Yes. OKX has a dedicated demo trading environment at https://www.okx.com with the header x-simulated-trading: 1 added to requests. You use real API keys but all orders execute in a simulated environment with no real funds at risk. This is essential for testing your authentication setup before going live.
How often should I rotate my OKX API keys?
Rotate API keys every 60-90 days as a security baseline. Immediately rotate if you suspect exposure — leaked keys in logs, public repos, or error messages. Use read-only keys wherever possible and only enable trade permissions on keys that explicitly need it. Never enable withdrawal permissions unless your use case requires it.
Can I use the same authentication code for OKX futures and spot APIs?
Yes — OKX uses the same four-header authentication scheme across all product types (spot, futures, options, perpetuals). Only the endpoint paths differ. With OKX's unified account, one set of credentials covers all products, unlike some exchanges where you need separate sub-accounts.
What's the timestamp tolerance for OKX API requests?
OKX rejects requests where the timestamp in the OK-ACCESS-TIMESTAMP header differs from OKX server time by more than 30 seconds. Generate the timestamp immediately before building headers and sending — don't cache it across multiple requests. If you're seeing timestamp errors, check your system clock against an NTP source.
Is there a rate limit on OKX authenticated endpoints?
Yes, OKX enforces per-endpoint rate limits measured in requests per second or per 2-second window. Trading endpoints like /api/v5/trade/order allow around 60 requests per 2 seconds per account. Exceeding the limit returns error code 50011. Implement exponential backoff and respect the Retry-After header when it's present.

Conclusion

OKX API authentication comes down to four headers, one HMAC-SHA256 signature, and careful attention to how you serialize request bodies. The authentication pattern is consistent across every OKX endpoint — once it works for account balance, it works for order placement, position management, and market data. The code examples here give you a production-ready foundation: timestamp generation, signature building, response error handling, and clock sync checks. From here, the logical next step is wrapping these helpers into a lightweight OKX client class and connecting it to a signal feed — whether that's from VoiceOfChain or your own technical analysis engine — to close the loop from signal to execution automatically.

◈   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