Short answer: The documented Crypto.com API for trading is the Crypto.com Exchange API v1: REST at api.crypto.com/exchange/v1, WebSocket at stream.crypto.com, and keys created on the Exchange website, not in the App. Private calls carry an HMAC-SHA256 signature over the method, id, API key, sorted parameters and a millisecond nonce. As read on 6 November 2026, the developer documentation and the help center state different rate limits, and the UAT sandbox is invitation-only.
This guide covers the Crypto.com Exchange API v1 as read on official pages on 6 November 2026, with code run that day on public endpoints, a fake session and a local mock. No real order was placed, because that needs a real key.
This is technical education, not a recommendation of Crypto.com or any strategy, and automated trading can lose money. Availability depends on your jurisdiction and the platform'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.
Crypto.com API: the App, the Exchange and which docs are current
The documented trading API belongs to the Exchange; the App's Agent Key is separate, and the v2.1 documentation redirects to Exchange v1.
| Product | Key created in | Documented |
|---|---|---|
| Exchange API v1 | Exchange website, API Management | REST, WebSocket and FIX reference |
| App Agent Key | App: More, then Agent Key | Setup for OpenClaw, an AI agent tool: 30-day default expiry, weekly trading budget, no endpoint reference |
Despite the numbering, Exchange v1 replaced v2.1; per its introduction it trades spot, derivatives and margin. On 6 November 2026, old /v2/public/get-ticker calls still answered while /v2/public/get-instruments returned HTTP 404. The live list held 552 spot pairs, 417 perpetual swaps and 10 dated futures; derivatives carry a high risk of rapid loss, and availability varies by jurisdiction.
How to create an API key on Crypto.com Exchange
Create it on the Exchange website under Manage Account, then API Management; a new key can only read until you enable trading.
- Open the profile menu, then Manage Account and API Management, and create a key with a label and your 2FA code.
- Copy the secret at once: the help center says it is shown only at creation.
- Enable trading only if the software places orders, and leave withdrawal off.
The official pages disagree on IP restrictions. The developer reference calls a whitelist optional and names the menu User Center, then API; the help center article, updated 20 July 2026, requires one once trading or withdrawal is enabled. Plan for a fixed outbound IP address either way. Storage and rotation are covered in API key security for trading bots.
Crypto.com Exchange API signing: the exact string
Join method, id, API key, the parameter string and the nonce, HMAC-SHA256 the result with your secret, and send the hex digest as sig.
- Parameter string: keys sorted ascending, each followed by its value, no separators; lists in order; nested objects alike.
- Numbers in
paramsmust be strings, such as"12.34". - Nonce: UTC milliseconds, within 60 seconds of server time.
The docs' own inputs. For private/get-order-detail with id 11, key token, order_id 53287421324 and nonce 1587846358253, the string to sign is private/get-order-detail11tokenorder_id532874213241587846358253, and with secret secretKey the signature starts 02ef0a52.
| Input | JavaScript | Java | Python |
|---|---|---|---|
exec_inst: ["POST_ONLY"] (the docs' create-order example) | exec_instPOST_ONLY | exec_instPOST_ONLY | TypeError |
Boolean true | true | true | True |
| Nested object | Keys and values | Keys and values | Python's printed dict |
The JavaScript sample run in Node, with Node's HMAC in place of crypto-js, and the client below both sign the create-order example as 6c17b745917702f0, so follow JavaScript and Java. That example also pairs POST_ONLY with FILL_OR_KILL, which its own schema forbids. Other schemes are compared in API signature generation, and Fincode's HMAC signature generator shows a signed string byte by byte.
Crypto.com API rate limits and error codes
The developer reference limits each method per API key, mostly per 100 milliseconds, while the help center states 10 calls per second; build for the lower figure.
| Calls | Limit |
|---|---|
private/create-order, cancel-order, cancel-all-orders | 15 per 100 ms each |
private/get-order-detail | 30 per 100 ms |
private/get-trades, get-order-history | 1 per second |
| Other private methods | 3 per 100 ms each |
| Public REST market data | 100 per second each, per IP |
| WebSocket User API, Market Data | 150 and 100 per second, scope not stated |
The help center, updated 20 July 2026, instead calls 10 calls per URL per second its only limit, per account for user APIs and per IP for market APIs. No response checked on 6 November 2026, public or private, carried a rate-limit header, so count client-side with a token bucket rate limiter.
| HTTP | Code | Meaning | Seen live |
|---|---|---|---|
| 401 | 40101 | Key or signature wrong | UAT, made-up key: Authentication failure |
| 400 | 40102 | Nonce over 60 seconds off | UAT, stale nonce: Invalid nonce |
| 400 | 40004 | Argument missing or invalid | Production, unknown instrument |
| 429 | 42901 | Rate limit exceeded | Not provoked |
Live error bodies held only code and message, with numeric codes where examples show strings. The nonce check ran first: a made-up key with a 120-second-old nonce got 40102, so clock errors can hide key errors.
Crypto.com UAT sandbox: who gets access and what it shows
The UAT sandbox at uat-api.3ona.co is by invitation for institutional accounts, according to the help center, yet its public endpoints answered without an account on 6 November 2026.
| Check | Production | UAT |
|---|---|---|
| Instruments | 979 | 1,284 |
product_type, added 21 May 2026 | On 970 of 979 | On 228 of 1,284 |
| BTC_USDT ticker | Trading normally | 24-hour high, low and last trade all 80,064.00 on zero volume, while the best bid moved |
UAT prices therefore say nothing about fills. Without an invitation, test signing against a local mock and trading logic in a paper trading layer.
A tested Python client for the Crypto.com Exchange API
The client signs like the JavaScript and Java samples, sends numbers as strings, rejects ambiguous input and separates authentication from network errors.
Run on 6 November 2026 with Python 3.11 and requests 2.31: public calls reached production without a key, and order calls went to a fake session returning the 401 body UAT sent for a made-up key.
import hashlib, hmac, itertools, re, time, uuid
from decimal import Decimal
import requests
BASE = "https://api.crypto.com/exchange/v1" # UAT: https://uat-api.3ona.co/exchange/v1
SESSION = requests.Session()
PLAIN = re.compile(r"[0-9]{1,20}(\.[0-9]{1,18})?")
ids, sent = itertools.count(1), set()
class ApiError(Exception): pass
class AuthError(ApiError): pass
def param_str(obj, level=0):
if isinstance(obj, (dict, list)) and level >= 3:
raise ValueError("nested deeper than 3 levels: the doc samples disagree")
if isinstance(obj, dict):
return "".join(k + param_str(obj[k], level + 1) for k in sorted(obj))
if isinstance(obj, list):
return "".join(param_str(v, level + 1) for v in obj)
if isinstance(obj, bool):
return "true" if obj else "false"
if obj is None:
return "null"
if isinstance(obj, str):
return obj
raise TypeError(f"send numbers as strings, not {repr(obj)}")
def sign(method, req_id, key, params, nonce, secret):
text = f"{method}{req_id}{key}{param_str(params)}{nonce}"
return hmac.new(secret.encode(), text.encode(), hashlib.sha256).hexdigest()
def call(method, params=None, key="", secret=""):
params = params or {}
if method.startswith("private/"):
if not (key and secret): # before anything is sent
raise AuthError("API key and secret are required")
body = {"id": next(ids), "method": method, "api_key": key,
"params": params, "nonce": int(time.time() * 1000)}
body["sig"] = sign(method, body["id"], key, params, body["nonce"], secret)
r = SESSION.post(f"{BASE}/{method}", json=body, timeout=10)
else:
r = SESSION.get(f"{BASE}/{method}", params=params, timeout=10)
try:
data = r.json()
code = int(data.get("code", -1)) # docs show "0", the live API sends 0
except (ValueError, AttributeError, TypeError):
raise ApiError(f"HTTP {r.status_code}: unexpected body") from None
if r.status_code == 200 and code == 0:
return data.get("result", {})
auth = r.status_code == 401 or code in (40101, 40103, 40104)
raise (AuthError if auth else ApiError)(f"HTTP {r.status_code} code {code}: {data.get('message', '')}")
def instrument(symbol):
rows = [x for x in call("public/get-instruments").get("data", []) if x.get("symbol") == symbol]
if len(rows) == 1:
return rows[0]
raise ApiError(f"{len(rows)} instruments named {symbol}")
def to_wire(value, tick):
try:
if PLAIN.fullmatch(value) and Decimal(value) > 0 and Decimal(value) % Decimal(tick) == 0:
return value
except (TypeError, ArithmeticError): # non-strings, too many digits
pass
raise ValueError(f"rejected {repr(value)} for tick {tick}")
def limit_order(inst, side, qty, price, key, secret, client_oid=""):
if not (key and secret):
raise AuthError("API key and secret are required")
client_oid = client_oid or str(uuid.uuid4()) # empty means new, never a duplicate
if len(client_oid) > 36 or client_oid in sent:
raise ValueError(f"client_oid {client_oid} is too long or already used")
params = {"instrument_name": inst["symbol"], "side": side, "type": "LIMIT",
"price": to_wire(price, inst["price_tick_size"]),
"quantity": to_wire(qty, inst["qty_tick_size"]),
"client_oid": client_oid, "time_in_force": "GOOD_TILL_CANCEL"}
sent.add(client_oid) # kept on failure: look the order up before resending
return call("private/create-order", params, key, secret)
# --- demo
inst = instrument("BTC_USDT")
print("ticks:", inst["price_tick_size"], inst["qty_tick_size"])
for v in ("1e400", "1e-400", 0.1, "75000.005"):
try:
to_wire(v, inst["price_tick_size"])
except ValueError as err:
print(err)
try:
call("public/get-book", {"instrument_name": "NOPE_USDT", "depth": "10"})
except ApiError as err:
print("live:", err)
doc = {"instrument_name": "BTCUSD-PERP", "side": "SELL", "type": "LIMIT", "price": "50000.5",
"quantity": "1", "client_oid": "c5f682ed-7108-4f1c-b755-972fcdca0f02",
"exec_inst": ["POST_ONLY"], "time_in_force": "FILL_OR_KILL"}
print("doc example:", sign("private/create-order", "1", "token", doc, "1610905028000", "secretKey")[:16])
class FakeSession:
posts, status_code = 0, 401
def post(self, *args, **kwargs):
FakeSession.posts += 1
return self
def json(self):
return {"code": 40101, "message": "Authentication failure"}
SESSION = FakeSession()
for key, oid in (("", ""), ("k", ""), ("k", ""), ("k", "a1"), ("k", "a1")):
try:
limit_order(inst, "BUY", "0.00010", "50000.00", key, "s", oid)
except (ApiError, ValueError) as err:
print(f"key={repr(key)} oid={repr(oid)} posts={FakeSession.posts}:", err)
Output:
ticks: 0.01 0.00001
rejected '1e400' for tick 0.01
rejected '1e-400' for tick 0.01
rejected 0.1 for tick 0.01
rejected '75000.005' for tick 0.01
live: HTTP 400 code 40004: Invalid instrument_name
doc example: 6c17b745917702f0
key='' oid='' posts=0: API key and secret are required
key='k' oid='' posts=1: HTTP 401 code 40101: Authentication failure
key='k' oid='' posts=2: HTTP 401 code 40101: Authentication failure
key='k' oid='a1' posts=3: HTTP 401 code 40101: Authentication failure
key='k' oid='a1' posts=3: client_oid a1 is too long or already used
A local mock that verifies like the docs' Java sample accepted this client's orders, lists and booleans, and returned 40101 for a wrong secret and 40102 for a clock 61 seconds slow. A refused connection raised a requests error and kept its order ID reserved.
Crypto.com WebSocket API: auth, heartbeat and order updates
Use wss://stream.crypto.com/exchange/v1/user for orders after public/auth, and /exchange/v1/market on the same host for public data without a key.
- Wait a second. Limits are pro-rated from the calendar second the connection opened; the docs advise a 1-second pause before the first request.
- Heartbeats. Per the docs, the server sends
public/heartbeatevery 30 seconds and expectspublic/respond-heartbeatwith the same id within 5. In two tests on 6 November 2026, a market-data connection that never replied was closed with code 1000 about 63 seconds after the first heartbeat it ignored; do not rely on that slack. - Orders are asynchronous.
private/create-orderconfirms the request,user.orderthe order. Withoutclient_oidthe nonce becomes the ID, so set your own (36 characters at most); the REST and WebSocket pages do not say how long an ID stays reserved. - Cancel on disconnect.
private/set-cancel-on-disconnecttakes scope CONNECTION, and its page says nothing more, so test it first. - Subscriptions. Error 40107 caps them per session; the limits section that the change log of 11 December 2023 cites is not in the current docs.
See WebSocket heartbeat and reconnect, order state machine and client order ID. For integration work in general, see Fincode's exchange API integration page; naming Crypto.com here does not mean Fincode integrates it or has built on it.
Frequently asked questions
Does the Crypto.com App have an API?
Its help pages describe an Agent Key for an AI agent tool, with no endpoint reference. The documented trading API is the Exchange API v1.
Is there a Crypto.com API sandbox?
UAT at uat-api.3ona.co, by invitation for institutional accounts according to the help center. Its public endpoints answered without an account on 6 November 2026.
Why does my Crypto.com API signature fail?
Common causes are JSON numbers instead of strings, unsorted keys, booleans written as True and the wrong secret. A stale nonce returns 40102 instead, and an unlisted IP 40103.
Where is the Crypto.com Exchange API documentation?
At exchange-developer.crypto.com/exchange/v1. Old exchange-docs.crypto.com links and the Spot API v2.1 page lead there.
Sources
- Crypto.com Exchange API v1: Common API Reference (endpoints, rate limits, signing, error codes) (opens in a new tab)
- Crypto.com Exchange API v1: REST API introduction (API versions) (opens in a new tab)
- Crypto.com Exchange API v1: Change Log (latest entry 21 May 2026) (opens in a new tab)
- Crypto.com Exchange API v1: private/create-order (opens in a new tab)
- Crypto.com Exchange API v1: public/get-instruments (opens in a new tab)
- Crypto.com Exchange API v1: public/get-tickers (ticker field definitions) (opens in a new tab)
- Crypto.com Exchange API v1: public/heartbeat (WebSocket) (opens in a new tab)
- Crypto.com Exchange API v1: private/set-cancel-on-disconnect (WebSocket) (opens in a new tab)
- Crypto.com Help Center: API (Exchange key creation, limits, UAT access; updated 20 July 2026) (opens in a new tab)
- Crypto.com Help Center: Agent Key, Getting Started (App; updated 17 March 2026) (opens in a new tab)
- Crypto.com Help Center: Agent Key, API Key Management (App; updated 27 February 2026) (opens in a new tab)
- Crypto.com Spot API v2.1 documentation (now redirects to Exchange v1) (opens in a new tab)
- Crypto.com Exchange public/get-instruments, live production response (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.