Short answer: There is no working KuCoin sandbox as of 5 November 2026: KuCoin announced in June 2023 that it would suspend it on 10 July 2023 and return it “in a new form later”, and no relaunch was found. To test the KuCoin API in Python, use public endpoints, a local mock that checks signatures, and the Classic order test endpoint, which validates an order without matching it.
Ranking library docs still describe a sandbox at sandbox.kucoin.com, and KuCoin's archived demo code lists openapi-sandbox.kucoin.com; neither resolves. Facts below come from KuCoin's pages, repositories and PyPI, and from keyless tests, all on 5 November 2026 between 19:04 and 20:41 UTC.
This is a technical guide, not a recommendation of KuCoin or any strategy; automated trading can lose money, and availability depends on your jurisdiction and KuCoin's terms. For readers in Turkey: Article 99/A of Capital Markets Law No. 6362 treats activity by foreign crypto-asset platforms directed at Turkish residents as unauthorized. For architecture in general, see crypto exchange API integration.
Is there a KuCoin sandbox or testnet in 2026?
No: the last official word is a 2023 suspension notice.
| Check | Result |
|---|---|
| KuCoin announcement, 29 June 2023 | Sandbox web and API services to be suspended on 10 July 2023 and to return “in a new form later” |
| Relaunch notice | None found; other sandbox notices date from 2019 to March 2023 |
| API documentation | No sandbox, testnet or demo mode in the docs index, introduction, authentication or rate limit pages, or the change log back to 1 December 2024 |
| DNS at 19:05 and 19:59 UTC | openapi-sandbox.kucoin.com, sandbox.kucoin.com, api-sandbox-futures.kucoin.com, sandbox-docs.kucoin.com: non-existent domain on the local resolver, 1.1.1.1 and 8.8.8.8 |
| Official SDK 1.3.2 | Production endpoints only |
| Archived official code | kucoin-api-demo still lists the sandbox URL; kucoin-python-sdk is unmaintained |
Code aimed at the old host fails at DNS lookup, before any key check.
How to test the KuCoin API without a sandbox
Test in layers, from no key to real money.
| Layer | Needs | Shows |
|---|---|---|
| Public REST and WebSocket | Nothing | Hosts, symbol rules, weights, message formats |
| Local mock | Synthetic keys | Signing, error handling, 429 waits, duplicate IDs, bad numbers |
| Order test endpoint | Real key with trade permission | That KuCoin accepts signature, permission and parameters |
| Smallest live order | Real key and funds | Everything, at real risk |
Spot's POST /api/v1/hf/orders/test (page modified 3 August 2026, weight 1) takes a real order's parameters and checks the signature without sending it to matching. Futures has POST /api/v1/orders/test (weight 2) on api-futures.kucoin.com, where the official SDK sends it; on api.kucoin.com that path is an abandoned spot endpoint. Futures carry high risk, because leverage magnifies losses. The index lists such endpoints for Classic accounts only, not UTA REST V2; both need a real key, so neither ran here. A live order is real and your own decision; for simulated fills, see the paper trading API guide.
KuCoin API Python: official SDK or plain requests
KuCoin's documentation points to one Python package, kucoin-universal-sdk; plain requests works too if you own the signing.
- The SDK. Its README calls it KuCoin's official SDK (1.3.2, released 15 September 2026, MIT license, marked Alpha on PyPI). On 5 November 2026 a keyless UTA order book call through it worked, and its signer matched the code below. By default it retries up to 3 times, including a GET or DELETE the server received (not a POST), and reconnects WebSockets every 5 seconds without limit.
- UTA V2. UTA REST V1 is now under Abandoned Endpoints; V2 paths start with
/api/ua/v2/.
| Account | REST | Public WebSocket | Private WebSocket |
|---|---|---|---|
| UTA | https://api.kucoin.com | wss://x-push-spot.kucoin.com, wss://x-push-futures.kucoin.com | wss://wsapi-push.kucoin.com; orders wss://wsapi.kucoin.com |
| Classic | https://api.kucoin.com; futures https://api-futures.kucoin.com | wss://ws-api-spot.kucoin.com, wss://ws-api-futures.kucoin.com, with a token | Returned with a private token |
For a multi-exchange library, see the CCXT Python guide.
KuCoin API signature in Python: passphrase, key version, encoding
Sign timestamp + method + path with query + body with HMAC-SHA256 and Base64, sign the passphrase the same way, and sign the query before URL encoding.
- Headers. The authentication page (modified 23 August 2026) requires six, all set in the code below; samples and SDK send key version 3.
- A conflict. The text says to sign the un-encoded URL, with a passphrase containing @ and # as its example, but the page's Python sample signs
urlencode(query_params). The SDK signs the raw form. Both agree when nothing needs encoding, so tests withBTC-USDTpass either way.
This client follows the text and the SDK. It ran on 5 November 2026 with Python 3.11, requests 2.31, synthetic keys and BTC-USDT steps from the public instrument endpoint.
import base64, hashlib, hmac, json, threading, time, uuid
from decimal import Decimal, InvalidOperation
from http.server import BaseHTTPRequestHandler, HTTPServer
from urllib.parse import unquote, urlencode
import requests
class KuCoinError(Exception):
pass
def sign(secret, msg):
return base64.b64encode(hmac.new(secret.encode(), msg.encode(), hashlib.sha256).digest()).decode()
def wire(value, step): # finite, positive, multiple of step
try:
d, s = Decimal(str(value)), Decimal(step)
ok = d.is_finite() and d > 0 and d == d.quantize(s) and d % s == 0
except InvalidOperation:
ok = False
if not ok:
raise ValueError(f"refused {repr(value)} (step {step})")
return format(d, "f")
class KuCoinREST:
def __init__(self, key, secret, passphrase, base="https://api.kucoin.com"):
if not (key and secret and passphrase): # before any network call
raise ValueError("key, secret and passphrase are all required")
self.key, self.secret, self.base, self.used = key, secret, base, set()
self.passphrase = sign(secret, passphrase)
def raw(self, path, params): # the signed form: not URL-encoded
return path + ("?" + "&".join(f"{k}={v}" for k, v in params.items()) if params else "")
def call(self, method, path, params=None, body=None):
data = json.dumps(body, separators=(",", ":")) if body else ""
ts = str(int(time.time() * 1000))
headers = {"KC-API-KEY": self.key, "KC-API-TIMESTAMP": ts, "KC-API-KEY-VERSION": "3",
"KC-API-SIGN": sign(self.secret, ts + method + self.raw(path, params) + data),
"KC-API-PASSPHRASE": self.passphrase, "Content-Type": "application/json"}
url = self.base + path + ("?" + urlencode(params) if params else "")
try:
r = requests.request(method, url, headers=headers, data=data or None, timeout=10)
except requests.RequestException as e:
raise KuCoinError(f"no answer ({type(e).__name__}): outcome unknown") from None
try:
js = r.json()
except ValueError:
js = None
js = js if isinstance(js, dict) else {}
code = str(js.get("code", ""))
if r.status_code == 429 or code == "429000":
raise KuCoinError(f"rate limited: wait {r.headers.get('gw-ratelimit-reset', '?')} ms")
if r.status_code == 200 and code == "200000":
return js.get("data")
raise KuCoinError(f"HTTP {r.status_code}, code {code or 'none'} {js.get('msg', '')}".strip())
def order_test(self, symbol, side, price, size, tick, step, client_oid=""):
oid = client_oid or uuid.uuid4().hex # empty: new ID, not a duplicate
body = {"clientOid": oid, "side": side, "symbol": symbol, "type": "limit",
"price": wire(price, tick), "size": wire(size, step)}
if oid in self.used:
raise ValueError(f"clientOid {oid} already used")
self.used.add(oid)
return self.call("POST", "/api/v1/hf/orders/test", body=body)
class EncodedSigning(KuCoinREST):
def raw(self, path, params):
return path + ("?" + urlencode(params) if params else "")
SECRET, PASS = "synthetic-secret", "synthetic-pass"
class Mock(BaseHTTPRequestHandler):
def do_POST(self):
body = self.rfile.read(int(self.headers.get("Content-Length") or 0)).decode()
prehash = self.headers["KC-API-TIMESTAMP"] + self.command + unquote(self.path) + body
if self.path == "/limited":
status, reply = 429, b"<html>Too Many Requests</html>"
elif (self.headers["KC-API-SIGN"] == sign(SECRET, prehash)
and self.headers["KC-API-PASSPHRASE"] == sign(SECRET, PASS)):
status, reply = 200, b'{"code":"200000","data":{"orderId":"mock-1"}}'
else:
status, reply = 401, b'{"code":"400005","msg":"Signature error"}'
self.send_response(status)
self.send_header("gw-ratelimit-reset", "812")
self.send_header("Content-Length", str(len(reply)))
self.end_headers()
self.wfile.write(reply)
do_GET = do_POST
log_message = lambda *args: None
srv = HTTPServer(("127.0.0.1", 0), Mock)
threading.Thread(target=srv.serve_forever, daemon=True).start()
local = f"http://127.0.0.1:{srv.server_port}"
kc = KuCoinREST("synthetic-key", SECRET, PASS, local)
enc = EncodedSigning("synthetic-key", SECRET, PASS, local)
odd = {"passphrase": "abc@#11"}
buy = lambda price, size, oid="": kc.order_test(
"BTC-USDT", "buy", price, size, "0.1", "0.00000001", oid)
tests = [
("raw, odd value", lambda: kc.call("GET", "/x", odd)),
("encoded, odd value", lambda: enc.call("GET", "/x", odd)),
("order test", lambda: buy("1000.1", "0.0001")),
("ID reused", lambda: [buy("1000.1", "0.0001", "a1") for _ in range(2)]),
("float 1e400", lambda: buy(float("1e400"), "1")),
("size 1e-9999999", lambda: buy("1000.1", "1e-9999999")),
("429, HTML body", lambda: kc.call("GET", "/limited")),
("closed port", lambda: KuCoinREST("k", SECRET, PASS, "http://127.0.0.1:9").call("GET", "/x")),
]
for label, fn in tests:
try:
print(label, "->", fn())
except (KuCoinError, ValueError) as e:
print(label, "->", type(e).__name__ + ":", e)
srv.shutdown()
Output:
raw, odd value -> {'orderId': 'mock-1'}
encoded, odd value -> KuCoinError: HTTP 401, code 400005 Signature error
order test -> {'orderId': 'mock-1'}
ID reused -> ValueError: clientOid a1 already used
float 1e400 -> ValueError: refused inf (step 0.1)
size 1e-9999999 -> ValueError: refused '1e-9999999' (step 0.00000001)
429, HTML body -> KuCoinError: rate limited: wait 812 ms
closed port -> KuCoinError: no answer (ConnectionError): outcome unknown
The mock applies the documented rule; KuCoin's verdict needs a real key. After a timeout on a real order, look it up by clientOid before resending. For errors such as 400003, see invalid API key, IP or permissions; for key setup, the exchange API key guide.
KuCoin API rate limit: pools, weights and the window
Private endpoints share a per-account pool and public ones a per-IP pool; the public pool measured on 5 November 2026 reset every 30 seconds, as the tables say, not the text.
| Item | Text | Table |
|---|---|---|
| Window | 1 second; HTTP 429, code 429000, once it is used up | Public 2,000/30s; Classic spot 4,000/30s at VIP0; UTA trading 300/s at VIP0 |
| VIP5 example | 700 per second | UTA trading 900/s |
At 19:09 and 20:02 UTC, keyless public calls carried gw-ratelimit-limit: 2000, with gw-ratelimit-reset counting down from 30,000 ms; quota fell by 5, 20, 20 and 50 for UTA order book depths 20, 21, 100 and 101, as documented (public since 2 November 2026). Private windows need a key and were not measured; read the three headers on every response. A 429000 without them means server overload, outside your quota. KuCoin's HTTP status page gives 403 for a breached limit, so test the code too.
Hypothetical example. Polling the UTA spot order book every second is 30 calls per window: 600 of the 2,000 public weight at depth 100 (weight 20), but 1,500 at depth 101 (weight 50), leaving 500 for everything else on that IP.
KuCoin WebSocket disconnects: ping, auth and reconnect
KuCoin drops connections on purpose, so ping on its interval, check the auth reply and reconnect after a pause.
- Planned drops. Load balancing may close UTA or Classic connections, at most once or twice a week by KuCoin's estimate (pages modified 11 October 2026); Classic tokens and connections last 24 hours.
- Ping. UTA welcome messages and the Classic token response gave
pingInterval18000 ms (pingTimeout10000 on private and Classic connections). Pinging more than once a second drops the connection. - Auth. On
wsapi-push.kucoin.com, sendop: authwith an HMAC of timestamp +POST/api/websocket/users/verifyaskc-api-sign(step 3 of the page mislabels itkc-api-passphrase) plus the signed passphrase. A synthetic key at 19:10 and 20:03 UTC got"result":false,"message":"auth failed", and the socket stayed open for at least 10 more seconds, so check the reply. - Format drift. All UTA frames arrived as binary frames holding JSON text; the subscribe acknowledgment had a boolean
truewhere the docs show a string, and the pong carriedtsin nanoseconds where they showtimestamp. - Reconnect. Wait 3 to 5 seconds, as the rate limit page asks, then resubscribe; each IP may open 300 new spot or private connections per 5 minutes.
Heartbeat code is in the WebSocket reconnect guide. For integration work in general, see exchange API integration; naming KuCoin here does not mean Fincode integrates it or has built on it.
Frequently asked questions
Does KuCoin have a testnet or sandbox in 2026?
Not as of 5 November 2026: KuCoin announced its suspension from 10 July 2023, and the current API documentation names no replacement.
What was the KuCoin sandbox URL?
KuCoin's archived demo repository lists https://openapi-sandbox.kucoin.com. It and sandbox.kucoin.com did not resolve on 5 November 2026.
Can I get a KuCoin testnet API key?
No. Keys created on kucoin.com are production keys, so keep withdrawal permission off, use the order test endpoint first and treat any live order as real.
Why does my KuCoin signature fail only on some requests?
Check whether the query was URL-encoded before signing: KuCoin's text and SDK sign the un-encoded form, its Python sample the encoded one, and only values with characters such as @ or # tell them apart.
Sources
- KuCoin: KuCoin Will Delist the Sandbox Mode (announcement, 29 June 2023) (opens in a new tab)
- KuCoin API: Introduction, UTA and Classic base URLs (modified 29 August 2026) (opens in a new tab)
- KuCoin API: Authentication (modified 23 August 2026) (opens in a new tab)
- KuCoin API: Rate Limit Rule (UTA), modified 11 October 2026 (opens in a new tab)
- KuCoin API: Rate Limit Rule (Classic), modified 11 October 2026 (opens in a new tab)
- KuCoin API: Change Log (read 5 November 2026) (opens in a new tab)
- KuCoin API: Add Order Test, Classic spot (modified 3 August 2026) (opens in a new tab)
- KuCoin API: UTA WebSocket V2 introduction (modified 11 October 2026) (opens in a new tab)
- KuCoin API: Classic WebSocket base info (modified 28 January 2026) (opens in a new tab)
- KuCoin API: Spot error codes (opens in a new tab)
- GitHub: Kucoin/kucoin-universal-sdk (README, version 1.3.2) (opens in a new tab)
- PyPI: kucoin-universal-sdk release history (opens in a new tab)
- GitHub: Kucoin/kucoin-python-sdk (archived, deprecation notice) (opens in a new tab)
- GitHub: Kucoin/kucoin-api-demo (archived; sandbox base URL in README) (opens in a new tab)
- Capital Markets Law No. 6362, Article 99/A (Turkish, consolidated text) (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.