Introduction
Welcome to the Crypto.com Exchange Broker Program API reference documentation.
Broker can manage orders for their customers by using API Key. Participate in broker program to earn
Customer API Key can be generated as these steps:
Customer selects crypto.com on broker UI
Customer authorizes broker to create API key for the selected account with crypto.com UI
figure 1 Customer authorization screen
- Broker receives access token for creating API key by the registered callback, a POST request with header "content-type: application/json"
Callback payload sample
{
"id": -1,
"method": "private/broker/broker-access-token-created",
"code": "0",
"detail_code": "0",
"result": {
"broker_reference_id": "cdc-authorization-1234567890123",
"broker_access_token": "78164676-f5f0-43d0-8e7a-e0a3244bd4d9"
}
}
Redirect customer to broker redirect destination
Broker creates API key with the access token
Broker onboarding for fast API
Register your user account and provide the following info for broker application.
- Logo - an image to display on our Authorization UI, prefer a 300x300 size image
Callback url for production and integration test, the callback will receive json payload containing the token and broker reference id
- sample request from our backend
curl -l [callback] -H 'content-type: application/json' -d '{"id": -1,"method": "private/broker/broker-access-token-created","code": "0","detail_code": "0","result": {"broker_reference_id": "[brokerReferenceId]","broker_access_token": "[accessToken]" }}'
- Test IP addresses - these addresses should be static and will be granted access to our integration test environment
You will receive broker id on successful onboarding. It will be used for customer authorization.
Use the following url for customer authorization page redirection
https://{Frontend_Route_Endpoint}/exchange/authorize_broker?broker_id={broker_id}&broker_reference_id={broker_reference_id}&redirect_uri={redirect_uri}
Name | Description |
---|---|
endpoint |
|
broker_id | broker id from onboarding |
broker_reference_id | a reference for receiving access token |
redirect_uri | redirect destination after access token generated |
Change Logs
- 2023-05-11 - initial
Breaking Change Schedule
Effective from 2023-05-11 (UAT), TBD (PROD)
Method | Breaking Change |
---|---|
- | - |
Generating the API Key
Before sending any requests, you need to generate a new API key.
This can be done in the Exchange website under User Center
- API
.
After generating the key, there are two things you need to carefully note down:
- API Key
- Secret Key
API Key and Secret are randomly generated by the system and can not be modified. Default settings will be set to "Can Read" only, and you have the option of adding or removing certain permissions for your API Key via Web UI.
You can optionally specify a whitelist of IP addresses when generating the API Key.
If specified, the API can only be used from the whitelisted IP addresses.
Rate Limits
REST API
For authenticated calls, rate limits are per API method, per API key:
Method | Limit |
---|---|
private/create-fast-api-key |
30 requests per 100ms |
Frontend Route Endpoint
UAT
https://xsta2-www.3ona.co/
Production
https://crypto.com/
REST API Root Endpoint
UAT Sandbox
REST API
https://uat-api.3ona.co/v2/{method}
Production (Effective: TBD)
https://api.crypto.com/v2/{method}
Common API Reference
Most of the APIs for REST and Websockets are shared, and follow the same request format and response, allowing users to easily switch between the two methods.
The Applies To
section under each API allows you to see which platform supports the API.
Naming Conventions
All methods and URLs in dash-case
All parameters in snake_case
Enums in full uppercase and snake_case
Request Format
The following information applies to both REST API and websockets commands:
Name | Type | Required | Description |
---|---|---|---|
id | long | N | Request Identifier Range: 0 to 9,223,372,036,854,775,807 Response message will contain the same id |
method | string | Y | The method to be invoked |
params | object | N | Parameters for the methods |
api_key | string | Depends | API key. See Digital Signature section |
sig | string | Depends | Digital signature. See Digital Signature section |
nonce | long | Y | Current timestamp (milliseconds since the Unix epoch) |
Digital Signature
const crypto = require("crypto-js");
const signRequest = (request_body, api_key, secret) => {
const { id, method, params, nonce } = request_body;
function isObject(obj) { return obj !== undefined && obj !== null && obj.constructor == Object; }
function isArray(obj) { return obj !== undefined && obj !== null && obj.constructor == Array; }
function arrayToString(obj) { return obj.reduce((a,b) => { return a + (isObject(b) ? objectToString(b) : (isArray(b) ? arrayToString(b) : b)); }, ""); }
function objectToString(obj) { return (obj == null ? "" : Object.keys(obj).sort().reduce((a, b) => { return a + b + (isArray(obj[b]) ? arrayToString(obj[b]) : (isObject(obj[b]) ? objectToString(obj[b]) : obj[b])); }, "")); }
const paramsString = objectToString(params);
console.log(paramsString);
const sigPayload = method + id + api_key + paramsString + nonce;
request_body.sig = crypto.HmacSHA256(sigPayload, secret).toString(crypto.enc.Hex);
};
const apiKey = "token"; /* User API Key */
const apiSecret = "secretKey"; /* User API Secret */
let request = {
id: 11,
method: "private/get-order-detail",
api_key: API_KEY,
params: {
order_id: 53287421324
},
nonce: 1587846358253,
};
const requestBody = JSON.stringify(signRequest(request, apiKey, apiSecret)));
import hmac
import hashlib
import time
API_KEY = "API_KEY"
SECRET_KEY = "SECRET_KEY"
req = {
"id": 14,
"method": "private/create-order-list",
"api_key": API_KEY,
"params": {
"contingency_type": "LIST",
"order_list": [
{
"instrument_name": "ONE_USDT",
"side": "BUY",
"type": "LIMIT",
"price": "0.24",
"quantity": "1.0"
},
{
"instrument_name": "ONE_USDT",
"side": "BUY",
"type": "STOP_LIMIT",
"price": "0.27",
"quantity": "1.0",
"trigger_price": "0.26"
}
]
},
"nonce": int(time.time() * 1000)
}
# First ensure the params are alphabetically sorted by key
param_str = ""
MAX_LEVEL = 3
def params_to_str(obj, level):
if level >= MAX_LEVEL:
return str(obj)
return_str = ""
for key in sorted(obj):
return_str += key
if obj[key] is None:
return_str += 'null'
elif isinstance(obj[key], list):
for subObj in obj[key]:
return_str += params_to_str(subObj, ++level)
else:
return_str += str(obj[key])
return return_str
if "params" in req:
param_str = params_to_str(req['params'], 0)
payload_str = req['method'] + str(req['id']) + req['api_key'] + param_str + str(req['nonce'])
req['sig'] = hmac.new(
bytes(str(SECRET_KEY), 'utf-8'),
msg=bytes(payload_str, 'utf-8'),
digestmod=hashlib.sha256
).hexdigest()
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using System.Web;
using System.Net.WebSockets;
private const string API_KEY = "YOUR_API_KEY";
private const string API_SECRET = "YOUR_API_SECRET";
private static string GetSign (Dictionary Request)
{
Dictionary Params = Request.Params;
// Ensure the params are alphabetically sorted by key
string ParamString = string.Join("", Params.Keys.OrderBy(key => key).Select(key => key + Params[key]));
string SigPayload = Request.method + Request.id + API_KEY + ParamString + Request.nonce;
var hash = new HMACSHA256(API_SECRET);
var ComputedHash = hash.ComputeHash(SigPayload);
return ToHex(ComputedHash, false);
}
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Map;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ApiRequestJson {
private Long id;
private String method;
private Map<String, Object> params;
private String sig;
@JsonProperty("api_key")
private String apiKey;
private Long nonce;
}
//------------
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.codec.binary.Hex;
public class SigningUtil {
private static final String HMAC_SHA256 = "HmacSHA256";
private static final int MAX_LEVEL = 3;
public static boolean verifySignature(ApiRequestJson apiRequestJson, String secret) {
try {
return genSignature(apiRequestJson, secret).equalsIgnoreCase(apiRequestJson.getSig());
} catch (Exception e) {
return false;
}
}
@SuppressWarnings("unchecked")
public static String getParamString(final Object paramObject) {
StringBuilder sb = new StringBuilder();
appendParamString(sb, paramObject, 0);
return sb.toString();
}
@SuppressWarnings("unchecked")
private static void appendParamString(final StringBuilder paramsStringBuilder, final Object paramObject, final int level) {
if (level >= MAX_LEVEL) {
paramsStringBuilder.append(paramObject.toString());
return;
}
if (paramObject instanceof Map) {
TreeMap<String, Object> params = new TreeMap<>((Map) paramObject);
for (Map.Entry<String, Object> entry : params.entrySet()) {
if (entry.getValue() instanceof Double) {
paramsStringBuilder
.append(entry.getKey())
.append((new BigDecimal(entry.getValue().toString()))
.stripTrailingZeros()
.toPlainString());
} else if (entry.getValue() instanceof List || entry.getValue() instanceof Map) {
paramsStringBuilder
.append(entry.getKey());
appendParamString(paramsStringBuilder, entry.getValue(), level + 1);
} else {
paramsStringBuilder
.append(entry.getKey())
.append(entry.getValue());
}
}
} else if (paramObject instanceof List) {
List list = (List) paramObject;
for (Object o : list) {
appendParamString(paramsStringBuilder, o, level + 1);
}
} else {
paramsStringBuilder.append(paramObject.toString());
}
}
public static String genSignature(ApiRequestJson apiRequestJson, String secret)
throws NoSuchAlgorithmException, InvalidKeyException {
final byte[] byteKey = secret.getBytes(StandardCharsets.UTF_8);
Mac mac = Mac.getInstance(HMAC_SHA256);
SecretKeySpec keySpec = new SecretKeySpec(byteKey, HMAC_SHA256);
mac.init(keySpec);
String paramsString = "";
if (apiRequestJson.getParams() != null) {
paramsString += getParamString(apiRequestJson.getParams());
}
String sigPayload =
apiRequestJson.getMethod()
+ apiRequestJson.getId()
+ apiRequestJson.getApiKey()
+ paramsString
+ (apiRequestJson.getNonce() == null ? "" : apiRequestJson.getNonce());
byte[] macData = mac.doFinal(sigPayload.getBytes(StandardCharsets.UTF_8));
return Hex.encodeHexString(macData);
}
public static ApiRequestJson sign(ApiRequestJson apiRequestJson, String secret)
throws InvalidKeyException, NoSuchAlgorithmException {
apiRequestJson.setSig(genSignature(apiRequestJson, secret));
return apiRequestJson;
}
public static void main(String[] argv) throws InvalidKeyException, NoSuchAlgorithmException {
ApiRequestJson apiRequestJson = ApiRequestJson.builder()
.id(11L)
.apiKey("token")
.method("public/auth")
.nonce(1589594102779L)
.build();
System.out.println(genSignature(apiRequestJson, "secretKey"));
System.out.println(sign(apiRequestJson, "secretKey"));
}
}
For REST API, only the private methods require a Digital Signature (as "sig") and API key (as "api_key") to be passed in. These private endpoints are only accessible by authenticated users.
The authentication is based on the pairing of the API Key, along with the HMAC-SHA256 hash of the request parameters using the API Secret as the cryptographic key.
The algorithm for generating the HMAC-SHA256 signature is as follows:
If "params" exist in the request, sort the request parameter keys in ascending order.
Combine all the ordered parameter keys as
key
+value
(no spaces, no delimiters). Let's call this theparameter string
Next, do the following:
method
+id
+api_key
+parameter string
+nonce
Use HMAC-SHA256 to hash the above using the API Secret as the cryptographic key
Encode the output as a hex string -- this is your Digital Signature
Digital Signature Issues
Based on the language, and the way data-types are handled, the digital signature may be incorrectly calculated for certain values.
One known case is if parameters contain numbers, and the value passed in is a whole numbers that still has decimal suffixes, e.g. 8000.000
instead of 8000
.
When the JSON request is sent to the server, the client (when parsing as JSON) may cast the number 8000.000
as 8000
automatically, which is what the server would see.
However, the digital signature may have been calculated using 8000.000
in the payload, which would result in a digital signature mismatch.
If this scenario happens in your case, there are three options:
Don't allow whole number values to have decimals
Ensure the payload casts number values properly before concatenating
Wrap the values in quotation marks to make them strings -- this is allowed by the server even if the parameter is marked as a number data type
Response Format
Name | Type | Description |
---|---|---|
id | long | Original request identifier |
method | string | Method invoked |
result | object | Result object |
code | int | 0 for success, see below for full list |
message | string | (optional) For server or error messages |
original | string | (optional) Original request as a string, for error cases |
detail_code | string | Detail Response Code. Please refer to Exchange v1 API for the list of values. |
detail_message | string | Detail Message (if any). |
Response and Reason Codes
These codes are shared by both the response, and the reason
field for rejected orders.
Code | HTTP Status | Message Code | Description |
---|---|---|---|
0 | 200 | -- | Success |
10000 | 200 | PARTIAL_SUCCESS | Batch request processed successfully and some requests succeeded (E.g. Some orders created when creating a list of orders) Please refer to detail_code and detail_message for additional information. Please refer to Exchange v1 API for the list of values. |
10001 | 500 | SYS_ERROR | Malformed request, (E.g. not using application/json for REST)Please refer to detail_code and detail_message for additional information. Please refer to Exchange v1 API for the list of values. |
10002 | 401 | UNAUTHORIZED | Not authenticated, or key/signature incorrect Please refer to detail_code and detail_message for additional information. Please refer to Exchange v1 API for the list of values. |
10003 | 401 | IP_ILLEGAL | IP address not whitelisted Please refer to detail_code and detail_message for additional information. Please refer to Exchange v1 API for the list of values. |
10004 | 400 | BAD_REQUEST | Missing required fields Please refer to detail_code and detail_message for additional information. Please refer to Exchange v1 API for the list of values. |
10005 | 401 | USER_TIER_INVALID | Disallowed based on user tier Please refer to detail_code and detail_message for additional information. Please refer to Exchange v1 API for the list of values. |
10006 | 429 | TOO_MANY_REQUESTS | Requests have exceeded rate limits Please refer to detail_code and detail_message for additional information. Please refer to Exchange v1 API for the list of values. |
10007 | 400 | INVALID_NONCE | Nonce value differs by more than 30 seconds from server Please refer to detail_code and detail_message for additional information. Please refer to Exchange v1 API for the list of values. |
10008 | 400 | METHOD_NOT_FOUND | Invalid method specified Please refer to detail_code and detail_message for additional information. Please refer to Exchange v1 API for the list of values. |
10009 | 400 | INVALID_DATE_RANGE | Invalid date range Please refer to detail_code and detail_message for additional information. Please refer to Exchange v1 API for the list of values. |
10010 | 200 | FAIL | Batch request processed successfully but all requests failed (E.g. No orders created when creating a list of orders) Please refer to detail_code and detail_message for additional information. Please refer to Exchange v1 API for the list of values. |
20001 | 400 | DUPLICATE_RECORD | Duplicated record Please refer to detail_code and detail_message for additional information. Please refer to Exchange v1 API for the list of values. |
20002 | 400 | NEGATIVE_BALANCE | Insufficient balance Please refer to detail_code and detail_message for additional information. Please refer to Exchange v1 API for the list of values. |
30003 | 400 | SYMBOL_NOT_FOUND | Invalid instrument_name specifiedPlease refer to detail_code and detail_message for additional information. Please refer to Exchange v1 API for the list of values. |
30004 | 400 | SIDE_NOT_SUPPORTED | Invalid side specifiedPlease refer to detail_code and detail_message for additional information. Please refer to Exchange v1 API for the list of values. |
30005 | 400 | ORDERTYPE_NOT_SUPPORTED | Invalid type specifiedPlease refer to detail_code and detail_message for additional information. Please refer to Exchange v1 API for the list of values. |
30006 | 400 | MIN_PRICE_VIOLATED | Price is lower than the minimum Please refer to detail_code and detail_message for additional information. Please refer to Exchange v1 API for the list of values. |
30007 | 400 | MAX_PRICE_VIOLATED | Price is higher than the maximum Please refer to detail_code and detail_message for additional information. Please refer to Exchange v1 API for the list of values. |
30008 | 400 | MIN_QUANTITY_VIOLATED | Quantity is lower than the minimum Please refer to detail_code and detail_message for additional information. Please refer to Exchange v1 API for the list of values. |
30009 | 400 | MAX_QUANTITY_VIOLATED | Quantity is higher than the maximum Please refer to detail_code and detail_message for additional information. Please refer to Exchange v1 API for the list of values. |
30010 | 400 | MISSING_ARGUMENT | Required argument is blank or missing Please refer to detail_code and detail_message for additional information. Please refer to Exchange v1 API for the list of values. |
30013 | 400 | INVALID_PRICE_PRECISION | Too many decimal places for Price Please refer to detail_code and detail_message for additional information. Please refer to Exchange v1 API for the list of values. |
30014 | 400 | INVALID_QUANTITY_PRECISION | Too many decimal places for Quantity Please refer to detail_code and detail_message for additional information. Please refer to Exchange v1 API for the list of values. |
30015 | 400 | REJECTION_FOR_EXEC_INST_POST_ONLY | The order is sent with "exec_inst=POST_ONLY", meanwhile, it can not be placed as resting active maker order Please refer to detail_code and detail_message for additional information. Please refer to Exchange v1 API for the list of values. |
30016 | 400 | MIN_NOTIONAL_VIOLATED | The notional amount is less than the minimum Please refer to detail_code and detail_message for additional information. Please refer to Exchange v1 API for the list of values. |
30017 | 400 | MAX_NOTIONAL_VIOLATED | The notional amount exceeds the maximum Please refer to detail_code and detail_message for additional information. Please refer to Exchange v1 API for the list of values. |
30023 | 400 | MIN_AMOUNT_VIOLATED | Amount is lower than the minimum Please refer to detail_code and detail_message for additional information. Please refer to Exchange v1 API for the list of values. |
30024 | 400 | MAX_AMOUNT_VIOLATED | Amount is higher than the maximum Please refer to detail_code and detail_message for additional information. Please refer to Exchange v1 API for the list of values. |
30025 | 400 | AMOUNT_PRECISION_OVERFLOW | Amount precision exceeds the maximum Please refer to detail_code and detail_message for additional information. Please refer to Exchange v1 API for the list of values. |
40001 | 400 | MG_INVALID_ACCOUNT_STATUS | Operation has failed due to your account's status. Please try again later. Please refer to detail_code and detail_message for additional information. Please refer to Exchange v1 API for the list of values. |
40002 | 400 | MG_TRANSFER_ACTIVE_LOAN | Transfer has failed due to holding an active loan. Please repay your loan and try again later. Please refer to detail_code and detail_message for additional information. Please refer to Exchange v1 API for the list of values. |
40003 | 400 | MG_INVALID_LOAN_CURRENCY | Currency is not same as loan currency of active loan Please refer to detail_code and detail_message for additional information. Please refer to Exchange v1 API for the list of values. |
40004 | 400 | MG_INVALID_REPAY_AMOUNT | Only supporting full repayment of all margin loans Please refer to detail_code and detail_message for additional information. Please refer to Exchange v1 API for the list of values. |
40005 | 400 | MG_NO_ACTIVE_LOAN | No active loan Please refer to detail_code and detail_message for additional information. Please refer to Exchange v1 API for the list of values. |
40006 | 400 | MG_BLOCKED_BORROW | Borrow has been suspended. Please try again later. Please refer to detail_code and detail_message for additional information. Please refer to Exchange v1 API for the list of values. |
40007 | 400 | MG_BLOCKED_NEW_ORDER | Placing new order has been suspended. Please try again later. Please refer to detail_code and detail_message for additional information. Please refer to Exchange v1 API for the list of values. |
50001 | 400 | DW_CREDIT_LINE_NOT_MAINTAINED | Please ensure your credit line is maintained and try again later. Please refer to detail_code and detail_message for additional information. Please refer to Exchange v1 API for the list of values. |
5000008 | 400 | SYSTEM_BUSY | System is currently busy, please try later. Please refer to detail_code and detail_message for additional information. Please refer to Exchange v1 API for the list of values. |
5000012 | 400 | CURRENCY_CLOSED | Currency is closed Please refer to detail_code and detail_message for additional information. Please refer to Exchange v1 API for the list of values. |
5000013 | 400 | BAD_PARAMETER | Bad parameter(s) Please refer to detail_code and detail_message for additional information. Please refer to Exchange v1 API for the list of values. |
5000808 | 403 | WITHDRAWAL_FORBIDDEN_ TEMPORARILY_UNAVAILABLE |
Withdrawal temporarily unavailable Please refer to detail_code and detail_message for additional information. Please refer to Exchange v1 API for the list of values. |
Websocket Termination Codes
Code | Description |
---|---|
1000 | Normal disconnection by server, usually when the heartbeat isn't handled properly |
1006 | Abnormal disconnection |
1013 | Server restarting -- try again later |
Fast API
Allow broker to create API key on behalf of customer. Customer does not need to take action to create and share API key.
private/broker/create-fast-api-key
Create API Key on behalf of the user with access token
Request Params
Request Sample
{
"id":"1",
"method": "private/broker/create-fast-api-key",
"params": {
"broker_access_token":"dddd1234-cafe-deee-9399-123443210000",
"broker_id":"00111234-cafe-deee-9399-123443210000",
"broker_reference_id":"69991111-cafe-deee-9399-123443210000",
"whitelist_ips":"8.8.8.8"
},
"nonce": 1680012321987
}
Name | Type | Required | Description |
---|---|---|---|
broker_access_token | string | Y | access token received from user authorization |
broker_id | string | Y | broker id |
broker_reference_id | string | Y | reference id of the user authorization |
whitelist_ips | string | N | IP whitelist for using the API KEY |
Applies To
REST Method
POST
Response Attributes
Response Sample
{
"id": "11",
"method": "private/broker/create-fast-api-key",
"code": 0,
"result": {
"ts": 1683013329206,
"api_key": "eUF7eDrkkM2jwfsnDqNATW",
"secret_key": "Xcow878VrkH6dSSkiKQyMh",
"enabled_trading": true,
"enabled_withdrawal": false
}
}
Name | Type | Description |
---|---|---|
ts | long | Timestamp |
api_key | string | API Key |
secret_key | string | API key secret |
enabled_trading | boolean | Trading enabled |
enabled_withdrawal | boolean | Trading enabled |
Websocket Heartbeats
Heartbeat Example
{
"id": 1587523073344,
"method": "public/heartbeat",
"code": 0
}
For websocket connections, the system will send a heartbeat message to the client every 30 seconds.
The client must respond back with the public/respond-heartbeat
method, using the same matching id
, within 5 seconds, or the connection will break.
public/respond-heartbeat
Request Sample
{
"id": 1587523073344,
"method": "public/respond-heartbeat"
}
Responds to the server-initiated websocket heartbeat
Request Params
None
Applies To
Common Issues
INVALID_NONCE On All Requests
The nonce should be the UTC Unix timestamp in milliseconds.
If this has been carefully checked, then the issue occurs when the system clock of the client machine is greater than 1 second in the future, or 30 seconds in the past.
Usually, re-syncing with the NTP time server on the client machine will correct the issue.
If the issue persists, you can try deliberately subtracting 5 seconds from the nonce to force it to be 5 seconds in the past, which is still within the 30-second past tolerance.