Short answer: To use the OKX API in Python, create a key, secret and passphrase, sign each private request with Base64 HMAC-SHA256 over timestamp + method + request path + body, and send it in OK-ACCESS headers to openapi.okx.com. OKX has no separate testnet REST host: demo trading uses that same host with a demo key and the header x-simulated-trading: 1. Public market data needs no key.
Debug signing with a sample or testnet key and budget your requests: HMAC signature generator and API rate limit calculator.
Three things stand out on OKX: a third credential, a test mode selected by a header, and an API agreement that restricts running the API as a service for others. This guide follows OKX's official V5 documentation and API Agreement as read on 4 November 2026; general concepts are in crypto exchange API integration.
What was run and what was not. Public REST and WebSocket samples ran on 4 November 2026 on Python 3.11.3. Signed calls reached OKX with a placeholder key and were rejected at the key check, so server acceptance of the signature, a demo order and live order-level errors are untested here.
This is a technical guide, not a recommendation to use OKX or any strategy, and availability depends on your jurisdiction and OKX's terms. For readers in Turkey, Article 99/A of Capital Markets Law No. 6362 treats activity by foreign-based crypto-asset platforms directed at Turkish residents as unauthorized service provision, so foreign platforms may not target them. Automated trading can lose money.
Does OKX have a testnet API? Demo trading explained
OKX has no separate test REST host: demo trading runs on the production host, selected by a header and a demo key, with its own WebSocket host.
| Item | Production | Demo trading |
|---|---|---|
| REST | https://openapi.okx.com | Same host, plus x-simulated-trading: 1 |
| WebSocket, public and private | wss://ws.okx.com/ws/v5/... | wss://wspap.okx.com/ws/v5/... |
| Key | Created on the API page | Created under Demo Trading in the personal center |
Because the host is shared, make the mode an explicit setting and refuse to start if header and key disagree. Error 50101 reports a key that does not match the environment. The docs say functions such as withdrawals, deposits and purchase or redemption are not supported in demo.
The overview also sets regional domains by where you registered: us.okx.com for US and Australian users of app.okx.com, and eea.okx.com for EU users of my.okx.com. It states that openapi.okx.com will not work for those regions. Demo results test software, not a strategy.
OKX API Python example: public market data
Public endpoints need no key, and the instrument record gives the increments every order must respect.
from decimal import Decimal, ROUND_DOWN
import requests
BASE = "https://openapi.okx.com" # us.okx.com or eea.okx.com for those registrations
def get(path, **params):
body = requests.get(BASE + path, params=params, timeout=10).json()
if int(body["code"]):
raise RuntimeError(body["code"], body["msg"])
return body["data"]
def floor_to(value, step):
step = Decimal(step)
return (Decimal(value) / step).to_integral_value(rounding=ROUND_DOWN) * step
print(get("/api/v5/public/time")[0]["ts"])
inst = get("/api/v5/public/instruments", instType="SPOT", instId="BTC-USDT")[0]
last = get("/api/v5/market/ticker", instId="BTC-USDT")[0]["last"]
price = floor_to(Decimal(last) * Decimal("0.95"), inst["tickSz"]) # hypothetical price
size = floor_to("0.0012345", inst["lotSz"]) # hypothetical size
print(inst["state"], last, inst["tickSz"], inst["lotSz"], inst["minSz"])
print(price, size.normalize(), size >= Decimal(inst["minSz"]))
Output on 4 November 2026:
1793821821441
live 80056.7 0.1 0.00000001 0.00001
76053.8 0.0012345 True
Hypothetical example. A limit 5% below 80,056.7 USDT is 76,053.865, which floors to 76,053.8 at a tickSz of 0.1. A size of 0.0012345 BTC already fits the lotSz and is above the minSz of 0.00001. This shows the rounding only.
OKX API key and passphrase: what to keep
An OKX API key has three parts, the key, the secret and a passphrase you choose, and a lost passphrase means creating a new key.
- Passphrase. OKX stores only a salted hash and says it cannot recover it.
- Permissions. Read, Trade and Withdraw. A trading bot has no need for Withdraw; see API key security for trading bots.
- IP binding. Each key can bind up to 20 addresses or ranges, and OKX strongly recommends it.
- Expiry. Keys with Trade or Withdraw and no bound IP expire after 14 days of inactivity; demo keys do not.
The account steps are in the exchange API key guide.
How to sign OKX API requests in Python
The signature is a Base64-encoded HMAC-SHA256 of timestamp + uppercase method + request path + body, using your secret.
import base64, hashlib, hmac, json, os
from datetime import datetime, timezone
import requests
BASE = "https://openapi.okx.com"
KEY = os.environ.get("OKX_DEMO_KEY", "00000000-0000-0000-0000-000000000000") # demo key only
SECRET = os.environ.get("OKX_DEMO_SECRET", "placeholder-secret")
PASSPHRASE = os.environ.get("OKX_DEMO_PASSPHRASE", "placeholder-passphrase")
def request(method, path, body=None):
text = json.dumps(body, separators=(",", ":")) if body else ""
ts = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%S.%f")[:-3] + "Z" # UTC, ms
prehash = ts + method + path + text # GET parameters belong in path
sign = base64.b64encode(hmac.new(SECRET.encode(), prehash.encode(),
hashlib.sha256).digest()).decode()
headers = {"OK-ACCESS-KEY": KEY, "OK-ACCESS-SIGN": sign, "OK-ACCESS-TIMESTAMP": ts,
"OK-ACCESS-PASSPHRASE": PASSPHRASE, "Content-Type": "application/json",
"x-simulated-trading": "1"} # demo trading; omit only for production
r = requests.request(method, BASE + path, data=text or None, headers=headers, timeout=10)
return r.status_code, r.json()
print(request("GET", "/api/v5/account/balance?ccy=USDT"))
print(request("POST", "/api/v5/trade/order",
{"instId": "BTC-USDT", "tdMode": "cash", "side": "buy", "ordType": "limit",
"px": "20000", "sz": "0.001", "clOrdId": "demo0001"}))
Output on 4 November 2026 with the placeholder credentials:
(401, {'msg': "API key doesn't exist", 'code': '50119'})
(401, {'msg': "API key doesn't exist", 'code': '50119'})
OKX stopped at the key, so this confirms the headers arrive, not that OKX accepts the signature. Offline, on the same inputs, the signature matched the signer in python-okx 0.4.4. With a demo key, the second call places a limit order priced far from the market.
- Timestamp. ISO 8601 in UTC with milliseconds; a difference of more than 30 seconds from server time returns 50102. OKX names local timezone offsets as the most common cause.
- Order results. When the response data carries
sCodeandsMsg, those fields hold the order's result, so a request can succeed while an order fails. clOrdId. Up to 32 case-sensitive letters and numbers, with no dashes; use it to find an order after a timeout.
OKX's docs link to python-okx on PyPI as their Python SDK, and it handles this signing. Release 0.4.4 (7 September 2026) defaults to www.okx.com, so pass the documented host:
import okx.MarketData as MarketData # pip install python-okx
import okx.Trade as Trade
HOST, DEMO = "https://openapi.okx.com", "1" # flag: "1" demo, "0" production
market = MarketData.MarketAPI(flag=DEMO, domain=HOST, debug=False)
print(market.get_ticker("BTC-USDT")["data"][0]["instId"])
trade = Trade.TradeAPI("00000000-0000-0000-0000-000000000000", "placeholder-secret",
"placeholder-passphrase", False, DEMO, domain=HOST, debug=False)
print(trade.place_order(instId="BTC-USDT", tdMode="cash", side="buy", ordType="limit",
px="20000", sz="0.001", clOrdId="demo0001"))
It printed BTC-USDT, then the same 50119 error. For a multi-exchange interface, see the CCXT Python guide.
OKX API key doesn't exist and other auth errors
Error 50119 means OKX found no key matching the value you sent, so check the key value, the regional host and whether the key has expired.
| Code | Message | Where to look |
|---|---|---|
| 50119 | API key doesn't exist | Copy error, deleted or expired key, wrong regional host |
| 50111 | Invalid OK-ACCESS-KEY | Malformed value; a non-UUID placeholder returned this in our test |
| 50101 | APIKey does not match current environment | Demo key without the header, or the reverse |
| 50113 | Invalid signature | Prehash string, query in the path, body sent differs from body signed |
| 50105 | Request header OK-ACCESS-PASSPHRASE incorrect | Passphrase; a lost one needs a new key |
| 50102 | Timestamp request expired | UTC format and clock sync |
The list gives messages, not causes, so the last column is our checklist. It also lists HTTP 200 for 50119, while our test received HTTP 401, so branch on the code field.
OKX API rate limits
OKX sets limits per endpoint and returns error 50011 when one is reached, while order-placement limits apply per instrument and are shared by REST and WebSocket.
| Scope | Limit | Basis | Error |
|---|---|---|---|
| Public REST | Per endpoint; the ticker is 20 requests per 2 seconds | IP | 50011 |
| Private REST | Per endpoint | User ID; sub-accounts have their own | 50011 |
| Place order | 60 requests per 2 seconds | User ID + instrument ID (options: instrument family) | 50011 |
| Sub-account orders | 1,000 new and amended orders per 2 seconds; higher tiers at VIP5 and above | Sub-account | 50061 |
| WebSocket connections | 3 per second | IP | Not stated |
| WebSocket requests | 480 subscribe, unsubscribe or login per hour | Connection | Not stated |
Placing, amending and canceling have independent limits, and batch endpoints are counted apart from single-order endpoints. The docs exempt spot and margin orders, among others, from the sub-account limit, but the per-instrument limit still applies. The Agreement separately forbids circumventing rate limits. Add your own limits as in trading bot risk management.
OKX WebSocket example in Python
Connect to the public host, send a subscribe message, and send the literal string ping when nothing has arrived for a period shorter than 30 seconds.
import asyncio, json
import websockets # pip install websockets
async def tickers(url, updates=2):
async with websockets.connect(url) as ws:
await ws.send(json.dumps({"op": "subscribe",
"args": [{"channel": "tickers", "instId": "BTC-USDT"}]}))
seen = 0
while seen < updates:
try:
raw = await asyncio.wait_for(ws.recv(), 25) # under the 30-second cut-off
except asyncio.TimeoutError:
await ws.send("ping") # the literal string; expect "pong"
continue
if raw == "pong":
continue
msg = json.loads(raw)
if "event" in msg:
print(msg["event"], msg.get("arg", {}).get("channel"))
elif "data" in msg:
seen += 1
print(msg["data"][0]["instId"], msg["data"][0]["bidPx"], msg["data"][0]["askPx"])
asyncio.run(tickers("wss://ws.okx.com/ws/v5/public")) # production
asyncio.run(tickers("wss://wspap.okx.com/ws/v5/public", 1)) # demo trading
Output on 4 November 2026:
subscribe tickers
BTC-USDT 80088 80088.1
BTC-USDT 80085.9 80086
subscribe tickers
BTC-USDT 80085.9 80086
OKX closes a connection with no subscription or no data for more than 30 seconds. Use port 443, the default: OKX's change log ends port 8443 on 31 October 2026, yet a 4 November test still connected, with notice 64010. Private channels need a login message signed in a similar way, which was not run because it needs a real key. Depth books follow the logic in the local order book guide.
OKX API Agreement Section 9.3 in plain words
Using the API for your own account is allowed, but running it as a service for others needs OKX's written approval.
Our plain reading of the Agreement (last updated 28 July 2026), which is not legal advice:
- Section 9.2. The license covers your own internal purposes and your own OKX account.
- Section 9.3. Without written authorization you may not resell the API, offer it as part of a commercial product, SaaS platform, brokerage, signal service, strategy marketplace or managed trading service, or execute orders for third parties.
- Section 9.4. Market data is for personal, non-commercial trading use, and it may not be redistributed or displayed to third parties without written consent. This applies to public endpoints too, which matters for dashboards and price widgets.
A multi-customer hosted bot or a public price page is the kind of thing these sections address, so get OKX's position in writing first. Terms differ by entity: OKX TR publishes its own agreement (dated 26 March 2026; page metadata shows a change on 16 September 2026) with a matching Section 9.3. A company named OKX TR Kripto Varlık Alım Satım Platformu AŞ is on the provisional list of Turkey's Capital Markets Board as checked on 4 November 2026, and the Board states that being listed does not mean a firm is authorized.
Frequently asked questions
Does OKX have a testnet?
Not as a separate REST host. Demo trading uses openapi.okx.com with a demo key and the x-simulated-trading: 1 header, and WebSocket uses wspap.okx.com.
Can I recover a lost OKX API passphrase?
No. OKX stores a salted hash and says it cannot recover the passphrase, so you need to create a new API key.
What does OKX API key doesn't exist mean?
It is error 50119. Check for a copy error, a deleted or expired key, and the regional host: keys from the US, Australian or EU sites use us.okx.com or eea.okx.com.
What is the OKX API rate limit error?
Error 50011 for an endpoint's limit, and 50061 for the sub-account order limit of 1,000 new and amended orders per 2 seconds, as listed on 4 November 2026.
Can I run an OKX bot as a service for clients?
Section 9.3 of the OKX API Agreement requires written authorization for commercial products, SaaS, signal services, managed trading and orders for third parties. Ask OKX in writing and take legal advice.
Sources
- OKX API v5: Overview (regional domains, API Agreement notice) (opens in a new tab)
- OKX API v5: API key creation (passphrase, permissions, IP binding, expiry) (opens in a new tab)
- OKX API v5: REST authentication (headers, signature) (opens in a new tab)
- OKX API v5: Demo trading services (opens in a new tab)
- OKX API v5: Rate limits (opens in a new tab)
- OKX API v5: WebSocket (limits, ping, port 8443 notice) (opens in a new tab)
- OKX API v5: Change log (port 8443 discontinuation, 22 October 2026) (opens in a new tab)
- OKX API v5: Place order (opens in a new tab)
- OKX API v5: REST error codes (opens in a new tab)
- OKX API Agreement (last updated 28 July 2026) (opens in a new tab)
- OKX TR API Agreement (Turkish, dated 26 March 2026, page metadata modified 16 September 2026) (opens in a new tab)
- PyPI: python-okx (release 0.4.4) (opens in a new tab)
- SPK: Provisional list of operating crypto-asset service providers (Turkish) (opens in a new tab)
- SPK: Announcement on crypto-asset service providers, 5 August 2024 (Turkish) (opens in a new tab)
- Capital Markets Law No. 6362 (consolidated text, Article 99/A, Turkish) (opens in a new tab)
This guide is general information about software and markets, not investment, legal or tax advice. Automated trading can lose money, past or backtested performance does not guarantee future results, and the rules that apply depend on your jurisdiction and your exchange's terms. Sources, checks and corrections: see the editorial policy.