NAV
shell java python javascript

Introduction

Welcome to the OpenAPI Documentation! This guide equips market makers with essential tools for seamless integration with our platform. Explore endpoints, request/response formats, authentication methods, and best practices to optimize your trading strategies.

Our support team is available for any assistance. Let's enhance financial markets together!

Changelog for OpenAPI

Last Updated: 2025-02-12

2025-02-12

REST API:

2024-12-04

REST API:


Prod urls

We have following base urls:

env Prod
UI https://m2.com
REST API https://openapi.m2.com
WS wss://stream.m2.com/endpoint

HTTP Response Handling

We have three types of responses:

  1. Normal response with a 200 HTTP header.
  2. Error response with a 4xx/5xx HTTP header.
  3. Error response with a 200 HTTP header (legacy part).

You need to handle all three cases to successfully integrate, especially the third one. For the third case, you should validate each response for internal body content to understand if you get an error or not. Your program should validate each 200 HTTP header response to ensure it doesn't contain keywords "code" and "msg". If it does, validate the string value of "code". If it's not 200, assume that you have an error.

Examples:

Normal Response: Status: 200

{
    "orderId": 3000000013200078,
    "clOrdId": "api_99313426_1699341538302",
    "symbol": "BTC-AED",
    "transactTime": 1699341538302
}

Error Response: Status: 400 (can be any 4xx status, but in most cases just 400)

{
    "code": "100200",
    "msg": "business error",
    "userMsg": "Invalid field for market buy order: orderQty"
}

Error Response: Status: 200 (handle this case too to ensure your 200 is not a real error)

{
    "code": "500",
    "msg": "amount_too_small",
    "data": null
}

API Key & Signature

All private endpoints, including 2 websocket private endpoints, require authentication using an apiKey.

Get apiKey & secretKey

Currently, you can't generate an apiKey from the UI directly. You would have to manually obtain it. Below is a 5-step example of how to get an apiKey:

  1. Login into the UI.
  2. Enable 2FA (settings -> security -> 2FA Authentication -> enable 2FA). Use Google Authenticator or any other Auth app to set up 2-factor-authentication.
  3. Extract Exchange-Token from UI (in the browser, go to `inspect -> network -> choose any request and check request headers for this token).
  4. Manually request email OTP for apiKey creation.
  5. Generate apiKey pair (apiKey & secretKey).

Step 3

# Set your token here
TOKEN=YOUR_TOKEN

Set Token

Step 4: This would send an email with OTP code with the subject "M2 Email Verification For Create Api key"

curl -d '{"operationType":7}' -H 'Content-Type: application/json' -H "exchange-token: $TOKEN" https://openapi.m2.com/user-center/common/sendEmailAuthCode
import java.net.HttpURLConnection;
import java.net.URL;
import java.io.OutputStream;

public class Main {
    public static void main(String[] args) throws Exception {
        String token = "YOUR_TOKEN_HERE";
        String url = "https://openapi.m2.com/user-center/common/sendEmailAuthCode";

        URL obj = new URL(url);
        HttpURLConnection con = (HttpURLConnection) obj.openConnection();
        con.setRequestMethod("POST");
        con.setRequestProperty("Content-Type", "application/json");
        con.setRequestProperty("exchange-token", token);

        String jsonInputString = "{\"operationType\":7}";

        con.setDoOutput(true);
        try (OutputStream os = con.getOutputStream()) {
            byte[] input = jsonInputString.getBytes("utf-8");
            os.write(input, 0, input.length);
        }

        int responseCode = con.getResponseCode();
        System.out.println("Response Code: " + responseCode);
    }
}
import requests

url = 'https://openapi.m2.com/user-center/common/sendEmailAuthCode'
token = 'YOUR_TOKEN_HERE'

payload = {'operationType': 7}
headers = {'Content-Type': 'application/json', 'exchange-token': token}

response = requests.post(url, json=payload, headers=headers)

print('Response Code:', response.status_code)
const axios = require('axios');

const url = 'https://openapi.m2.com/user-center/common/sendEmailAuthCode';
const token = 'YOUR_TOKEN_HERE';

const payload = { operationType: 7 };
const headers = {
  'Content-Type': 'application/json',
  'exchange-token': token
};

axios.post(url, payload, { headers })
  .then(response => {
    console.log('Response Code:', response.status);
  })
  .catch(error => {
    console.error('Error:', error);
  });

Step 5 (create apiKey with read & write privilege [5,6])

curl -d '{"emailAuthCode":"111111","googleAuthCode":"111111","alias":"my_key","moduleIds":[5,6]}' -H 'Content-Type: application/json' -H "exchange-token: $TOKEN" https://openapi.m2.com/user-center/v1/api/create
import java.net.HttpURLConnection;
import java.net.URL;
import java.io.OutputStream;

public class Main {
    public static void main(String[] args) throws Exception {
        String token = "YOUR_TOKEN_HERE";
        String url = "https://openapi.m2.com/user-center/v1/api/create";

        URL obj = new URL(url);
        HttpURLConnection con = (HttpURLConnection) obj.openConnection();
        con.setRequestMethod("POST");
        con.setRequestProperty("Content-Type", "application/json");
        con.setRequestProperty("exchange-token", token);

        String jsonInputString = "{\"emailAuthCode\":\"111111\",\"googleAuthCode\":\"111111\",\"alias\":\"my_key\",\"moduleIds\":[5,6]}";

        con.setDoOutput(true);
        try (OutputStream os = con.getOutputStream()) {
            byte[] input = jsonInputString.getBytes("utf-8");
            os.write(input, 0, input.length);
        }

        int responseCode = con.getResponseCode();
        System.out.println("Response Code: " + responseCode);
    }
}
import requests

url = 'https://openapi.m2.com/user-center/v1/api/create'
token = 'YOUR_TOKEN_HERE'

payload = {
    'emailAuthCode': '111111',
    'googleAuthCode': '111111',
    'alias': 'my_key',
    'moduleIds': [5, 6]
}
headers = {'Content-Type': 'application/json', 'exchange-token': token}

response = requests.post(url, json=payload, headers=headers)

print('Response Code:', response.status_code)
const axios = require('axios');

const url = 'https://openapi.m2.com/user-center/v1/api/create';
const token = 'YOUR_TOKEN_HERE';

const payload = {
  emailAuthCode: '111111',
  googleAuthCode: '111111',
  alias: 'my_key',
  moduleIds: [5, 6]
};
const headers = {
  'Content-Type': 'application/json',
  'exchange-token': token
};

axios.post(url, payload, { headers })
  .then(response => {
    console.log('Response Code:', response.status);
  })
  .catch(error => {
    console.error('Error:', error);
  });

When we create an apiKey, we should decide its privilege (what our key can access). When you create an apiKey, you can pass 1 or many moduleIds, depending on what access you want to provide for your apiKey. Currently, we have 4 levels of privilege:

Sign the request with secretKey

Example to request position API

# Generate signature using HMAC SHA256 algorithm
API_KEY=YOUR_API_KEY
SECRET=YOUR_SECRET
# Send GET request to validate read API (moduleId=5)
# Replace YOUR_TIMESTAMP with timestamp
curl -H 'Content-Type: application/json' -H "EXCHANGE-API-TIMESTAMP: YOUR_TIMESTAMP" -H "EXCHANGE-API-KEY: $API_KEY" -H "EXCHANGE-API-SIGN: $SIG" https://openapi.m2.com/spot-api/api/private/v1/positions
# Generate signature using HMAC SHA256 algorithm
# Replace YOUR_TIMESTAMP with timestamp
SIG=hmac('YOUR_TIMESTAMPPOST/spot-api/api/private/v1/order{"symbol":"BTC-AED","ordType":"2","orderQty":15,"price":"2.5", "side":"2"}', $SECRET)
# Send POST request to validate write API (moduleId=6)
# Replace YOUR_TIMESTAMP with timestamp
curl -H 'Content-Type: application/json' -H "EXCHANGE-API-TIMESTAMP: YOUR_TIMESTAMP" -H "EXCHANGE-API-KEY: $API_KEY" -H "EXCHANGE-API-SIGN: $SIG" -d '{"symbol":"BTC-AED","ordType":"2","orderQty":15,"price":"2.5","side":"2"}' https://openapi.m2.com/spot-api/api/private/v1/order
# Generate signature using HMAC SHA256 algorithm (pay attention that we don't use `?` in signature, we concatenate path+query without `?`)
# Replace YOUR_TIMESTAMP with timestamp
SIG=hmac('YOUR_TIMESTAMPGET/spot-api/api/private/v1/orderstype=COMPLETED&size=100', $SECRET)
# Send GET request to validate read API (moduleId=6) with "URL query params"
# Replace YOUR_TIMESTAMP with timestamp
curl -H 'Content-Type: application/json' -H "EXCHANGE-API-TIMESTAMP: YOUR_TIMESTAMP" -H "EXCHANGE-API-KEY: $API_KEY" -H "EXCHANGE-API-SIGN: $SIG" 'https://openapi.m2.com/spot-api/api/private/v1/orders?type=COMPLETED&size=100'
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.io.IOException;
import java.time.Instant;

public class Main {
 private static final String API_KEY = "YOUR_API_KEY";
    private static final String SECRET = "YOUR_SECRET_KEY";

    public static void main(String[] args) {
        sendGetRequestForPositions();
        sendPostRequestForOrder();
        sendGetRequestForCompletedOrders();
    }

    private static void sendGetRequestForPositions() {
        try {
            String url = "https://openapi.m2.com/spot-api/api/private/v1/positions";
            String timestamp = String.valueOf(Instant.now().toEpochMilli());
            String signature = generateSignature("GET", "/spot-api/api/private/v1/positions", "", timestamp);

            URL apiUrl = new URL(url);
            HttpURLConnection connection = (HttpURLConnection) apiUrl.openConnection();
            connection.setRequestMethod("GET");
            connection.setRequestProperty("Content-Type", "application/json");
            connection.setRequestProperty("EXCHANGE-API-TIMESTAMP", timestamp);
            connection.setRequestProperty("EXCHANGE-API-KEY", API_KEY);
            connection.setRequestProperty("EXCHANGE-API-SIGN", signature);

            // Handle response...
            handleResponse(connection);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private static void sendPostRequestForOrder() {
        try {
            String url = "https://openapi.m2.com/spot-api/api/private/v1/order";
            String timestamp = String.valueOf(Instant.now().toEpochMilli());
            String requestBody = "{\"symbol\":\"BTC-AED\",\"ordType\":\"2\",\"orderQty\":1,\"price\":\"100\",\"side\":\"2\"}";
            String signature = generateSignature("POST", "/spot-api/api/private/v1/order", requestBody, timestamp);

            URL apiUrl = new URL(url);
            HttpURLConnection connection = (HttpURLConnection) apiUrl.openConnection();
            connection.setRequestMethod("POST");
            connection.setRequestProperty("Content-Type", "application/json");
            connection.setRequestProperty("EXCHANGE-API-TIMESTAMP", timestamp);
            connection.setRequestProperty("EXCHANGE-API-KEY", API_KEY);
            connection.setRequestProperty("EXCHANGE-API-SIGN", signature);

            connection.setDoOutput(true);
            connection.getOutputStream().write(requestBody.getBytes(StandardCharsets.UTF_8));

            // Handle response...
            handleResponse(connection);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private static void sendGetRequestForCompletedOrders() {
        try {
            String url = "https://openapi.m2.com/spot-api/api/private/v1/orders?type=COMPLETED&size=100";
            String timestamp = String.valueOf(Instant.now().toEpochMilli());
            String signature = generateSignature("GET", "/spot-api/api/private/v1/orders", "type=COMPLETED&size=100", timestamp);

            URL apiUrl = new URL(url);
            HttpURLConnection connection = (HttpURLConnection) apiUrl.openConnection();
            connection.setRequestMethod("GET");
            connection.setRequestProperty("Content-Type", "application/json");
            connection.setRequestProperty("EXCHANGE-API-TIMESTAMP", timestamp);
            connection.setRequestProperty("EXCHANGE-API-KEY", API_KEY);
            connection.setRequestProperty("EXCHANGE-API-SIGN", signature);

            // Handle response...
            handleResponse(connection);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

 private static String generateSignature(String method, String path, String params, String timestamp) throws NoSuchAlgorithmException, InvalidKeyException {
        String data = timestamp + method + path + params;
        Mac mac = Mac.getInstance("HmacSHA256");
        SecretKeySpec secretKeySpec = new SecretKeySpec(SECRET.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
        mac.init(secretKeySpec);
        byte[] hmacData = mac.doFinal(data.getBytes(StandardCharsets.UTF_8));
        // convert bytes to hex
        StringBuilder result = new StringBuilder();
        for (byte b : hmacData) {
            result.append(String.format("%02x", b));
        }
        return result.toString();
    }

    private static void handleResponse(HttpURLConnection connection) throws IOException {
        int responseCode = connection.getResponseCode();
        System.out.println("Response Code: " + responseCode);

        BufferedReader in;
        if (responseCode == HttpURLConnection.HTTP_OK) {
            in = new BufferedReader(new InputStreamReader(connection.getInputStream()));
        } else {
            in = new BufferedReader(new InputStreamReader(connection.getErrorStream()));
        }

        StringBuilder response = new StringBuilder();
        String inputLine;
        while ((inputLine = in.readLine()) != null) {
            response.append(inputLine);
        }
        in.close();

        // Print the response body
        System.out.println("Response Body: " + response.toString());
    }
}
import requests
import hashlib
import hmac
import time
import json

API_KEY = "YOUR_API_KEY"
SECRET = "YOUR_API_SECRET"


def send_get_request_for_positions():
    try:
        url = "https://openapi.m2.com/spot-api/api/private/v1/positions"
        timestamp = str(int(time.time() * 1000))
        signature = generate_signature("GET", "/spot-api/api/private/v1/positions", "", timestamp)

        headers = {
            "Content-Type": "application/json",
            "EXCHANGE-API-TIMESTAMP": timestamp,
            "EXCHANGE-API-KEY": API_KEY,
            "EXCHANGE-API-SIGN": signature
        }

        response = requests.get(url, headers=headers)
        handle_response(response)

    except Exception as e:
        print(e)


def send_post_request_for_order():
    try:
        url = "https://openapi.m2.com/spot-api/api/private/v1/order"
        timestamp = str(int(time.time() * 1000))
        request_body = {
            "symbol": "BTC-AED",
            "ordType": "2",
            "orderQty": 1,
            "price": "100",
            "side": "2"
        }
        request_body_json = json.dumps(request_body)
        signature = generate_signature("POST", "/spot-api/api/private/v1/order", request_body_json, timestamp)

        headers = {
            "Content-Type": "application/json",
            "EXCHANGE-API-TIMESTAMP": timestamp,
            "EXCHANGE-API-KEY": API_KEY,
            "EXCHANGE-API-SIGN": signature
        }

        response = requests.post(url, headers=headers, data=request_body_json)
        handle_response(response)

    except Exception as e:
        print(e)


def send_get_request_for_completed_orders():
    try:
        url = "https://openapi.m2.com/spot-api/api/private/v1/orders?type=COMPLETED&size=100"
        timestamp = str(int(time.time() * 1000))
        signature = generate_signature("GET", "/spot-api/api/private/v1/orders", "type=COMPLETED&size=100", timestamp)

        headers = {
            "Content-Type": "application/json",
            "EXCHANGE-API-TIMESTAMP": timestamp,
            "EXCHANGE-API-KEY": API_KEY,
            "EXCHANGE-API-SIGN": signature
        }

        response = requests.get(url, headers=headers)
        handle_response(response)

    except Exception as e:
        print(e)


def generate_signature(method, path, params, timestamp):
    data = timestamp + method + path + params
    signature = hmac.new(SECRET.encode('utf-8'), data.encode('utf-8'), hashlib.sha256).hexdigest()
    return signature


def handle_response(response):
    print("Response Code:", response.status_code)
    print("Response Body:", response.text)


def main():
    send_get_request_for_positions()
    send_post_request_for_order()
    send_get_request_for_completed_orders()


if __name__ == "__main__":
    main()
const axios = require('axios');
const crypto = require('crypto');

const API_KEY = "YOUR_API_KEY"
const SECRET = "YOUR_SECRET_KEY"


function sendGetRequestForPositions() {
    try {
        const url = "https://openapi.m2.com/spot-api/api/private/v1/positions";
        const timestamp = Math.floor(Date.now());
        const signature = generateSignature("GET", "/spot-api/api/private/v1/positions", "", timestamp);

        const headers = {
            "Content-Type": "application/json",
            "EXCHANGE-API-TIMESTAMP": timestamp,
            "EXCHANGE-API-KEY": API_KEY,
            "EXCHANGE-API-SIGN": signature
        };

        axios.get(url, { headers })
            .then(response => handleResponse(response))
            .catch(error => console.error(error));
    } catch (error) {
        console.error(error);
    }
}

function sendPostRequestForOrder() {
    try {
        const url = "https://openapi.m2.com/spot-api/api/private/v1/order";
        const timestamp = Math.floor(Date.now());
        const requestBody = {
            symbol: "BTC-AED",
            ordType: "2",
            orderQty: 1,
            price: "100",
            side: "2"
        };
        const request_body_json = JSON.stringify(requestBody);
        const signature = generateSignature("POST", "/spot-api/api/private/v1/order", request_body_json, timestamp);

        const headers = {
            "Content-Type": "application/json",
            "EXCHANGE-API-TIMESTAMP": timestamp,
            "EXCHANGE-API-KEY": API_KEY,
            "EXCHANGE-API-SIGN": signature
        };

        axios.post(url, request_body_json, { headers })
            .then(response => handleResponse(response))
            .catch(error => console.error(error));
    } catch (error) {
        console.error(error);
    }
}

function sendGetRequestForCompletedOrders() {
    try {
        const url = "https://openapi.m2.com/spot-api/api/private/v1/orders?type=COMPLETED&size=100";
        const timestamp = Math.floor(Date.now());
        const signature = generateSignature("GET", "/spot-api/api/private/v1/orders", "type=COMPLETED&size=100", timestamp);

        const headers = {
            "Content-Type": "application/json",
            "EXCHANGE-API-TIMESTAMP": timestamp,
            "EXCHANGE-API-KEY": API_KEY,
            "EXCHANGE-API-SIGN": signature
        };

        axios.get(url, { headers })
            .then(response => handleResponse(response))
            .catch(error => console.error(error));
    } catch (error) {
        console.error(error);
    }
}

function generateSignature(method, path, params, timestamp) {
    const data = timestamp + method + path + params;
    const signature = crypto.createHmac('sha256', SECRET).update(data).digest('hex');
    return signature;
}

function handleResponse(response) {
    console.log("Response Code:", response.status);
    console.log("Response Body:", response.data);
}

function main() {
    sendGetRequestForPositions();
    sendPostRequestForOrder();
    sendGetRequestForCompletedOrders();
}

main();

REST API

Public Endpoints

getInstruments (deprecated)

Get all available instruments for trading.

Request

// Request params
{
    "symbol": "BTC-AED",
    "securityId": 1,
    "securityType": "SPOT_PAIR"
}
# Request
curl -X GET https://openapi.m2.com/spot-api/api/public/v1/instruments
// Request
import java.net.HttpURLConnection;
import java.net.URL;
import java.io.BufferedReader;
import java.io.InputStreamReader;

public class HttpRequest {
    public static void main(String[] args) {
        try {
            String url = "https://openapi.m2.com/spot-api/api/public/v1/instruments";
            URL apiUrl = new URL(url);
            HttpURLConnection connection = (HttpURLConnection) apiUrl.openConnection();
            connection.setRequestMethod("GET");
            connection.setRequestProperty("Content-Type", "application/json");

            // Handle response...
            int responseCode = connection.getResponseCode();

            System.out.println("Response Code: " + responseCode);

            BufferedReader in;
            if (responseCode == HttpURLConnection.HTTP_OK) {
                in = new BufferedReader(new InputStreamReader(connection.getInputStream()));
            } else {
                in = new BufferedReader(new InputStreamReader(connection.getErrorStream()));
            }

            StringBuilder response = new StringBuilder();
            String inputLine;
            while ((inputLine = in.readLine()) != null) {
                response.append(inputLine);
            }
            in.close();

            // Print the response body
            System.out.println("Response Body: " + response.toString());
        } catch (Exception e) {
              e.printStackTrace();
        }
    }
}
// Request
const axios = require("axios");

axios.get('https://openapi.m2.com/spot-api/api/public/v1/instruments')
    .then(response => {
        console.log(response.data);
    })
    .catch(error => {
        console.error('Error:', error);
    });
# Request
import requests

response = requests.get('https://openapi.m2.com/spot-api/api/public/v1/instruments')
print("Response Code:", response.status_code)
print("Response:", response.json())

Request Fields

json field name type required description
symbol string N symbol of instrument (used for filtering)
securityId int N unique integer ID of instrument
securityType string N type of position (used for filtering)

Search Rules:

Response

// Response
[
    {
        "symbol": "BTC-AED",
        "securityId": 1,
        "securityType": "SPOT_PAIR",
        "instrumentStatus": "LIVE",
        "baseAssetIncrement": 0.000001,
        "quoteAssetIncrement": 0.01,
        "baseAssetMinOrderSize": 0.01,
        "quoteAssetMinOrderSize": 100,
        "open": true,
        "fiat": false,
    }
]

Response Fields

json field name type description
symbol string symbol of instrument
securityId int unique integer ID of instrument
securityType string type of instrument
instrumentStatus string status of pair (only for SPOT_PAIR)
baseAssetIncrement decimal increment value for base asset
quoteAssetIncrement decimal increment value for quote asset
baseAssetMinOrderSize decimal min amount of base currency to submit the order
quoteAssetMinOrderSize decimal min amount of quote currency to submit the order
open boolean field to show whether trading is open
fiat boolean field to show whether it's a fiat instrument

Get all available instruments for trading.

Request

// Request params
{
    "symbol": "BTC-AED",
    "securityId": 1,
    "securityType": "SPOT_PAIR"
}
# Request
curl -X GET https://openapi.m2.com/spot-api/api/public/v2/instruments
// Request
import java.net.HttpURLConnection;
import java.net.URL;
import java.io.BufferedReader;
import java.io.InputStreamReader;

public class HttpRequest {
    public static void main(String[] args) {
        try {
            String url = "https://openapi.m2.com/spot-api/api/public/v2/instruments";
            URL apiUrl = new URL(url);
            HttpURLConnection connection = (HttpURLConnection) apiUrl.openConnection();
            connection.setRequestMethod("GET");
            connection.setRequestProperty("Content-Type", "application/json");

            // Handle response...
            int responseCode = connection.getResponseCode();

            System.out.println("Response Code: " + responseCode);

            BufferedReader in;
            if (responseCode == HttpURLConnection.HTTP_OK) {
                in = new BufferedReader(new InputStreamReader(connection.getInputStream()));
            } else {
                in = new BufferedReader(new InputStreamReader(connection.getErrorStream()));
            }

            StringBuilder response = new StringBuilder();
            String inputLine;
            while ((inputLine = in.readLine()) != null) {
                response.append(inputLine);
            }
            in.close();

            // Print the response body
            System.out.println("Response Body: " + response.toString());
        } catch (Exception e) {
              e.printStackTrace();
        }
    }
}
// Request
const axios = require("axios");

axios.get('https://openapi.m2.com/spot-api/api/public/v2/instruments')
    .then(response => {
        console.log(response.data);
    })
    .catch(error => {
        console.error('Error:', error);
    });
# Request
import requests

response = requests.get('https://openapi.m2.com/spot-api/api/public/v2/instruments')
print("Response Code:", response.status_code)
print("Response:", response.json())

Request Fields

json field name type required description
symbol string N symbol of instrument (used for filtering)
securityId int N unique integer ID of instrument
securityType string N type of position (used for filtering)

Search Rules:

Response

// Response
[
    {
        "symbol": "BTC-AED",
        "securityId": 1,
        "securityType": "SPOT_PAIR",
        "instrumentStatus": "LIVE",
        "baseAssetIncrement": 0.000001,
        "quoteAssetIncrement": 0.01,
        "baseAssetMinOrderSize": 0.01,
        "quoteAssetMinOrderSize": 100,
        "apiTradeSupported": true,
        "open": true,
        "fiat": false,
    }
]

Response Fields

json field name type description
symbol string symbol of instrument
securityId int unique integer ID of instrument
securityType string type of instrument
instrumentStatus string status of pair (only for SPOT_PAIR)
baseAssetIncrement decimal increment value for base asset
quoteAssetIncrement decimal increment value for quote asset
baseAssetMinOrderSize decimal min amount of base currency to submit the order
quoteAssetMinOrderSize decimal min amount of quote currency to submit the order
open boolean field to show whether trading is open
fiat boolean field to show whether it's a fiat instrument
apiTradeSupported boolean field to show whether it can support API spot trading

getOrderbook

Return the current orderbook with bids/asks.

Request

// Request parameters
{
    "symbol": "BTC-AED",
    "depth": 20
}
# Request
curl -X GET 'https://openapi.m2.com/market-data-server/api/public/v1/orderbook?symbol=BTC-AED&depth=20'
// Request
import java.net.HttpURLConnection;
import java.net.URL;
import java.io.BufferedReader;
import java.io.InputStreamReader;

public class HttpRequest {
    public static void main(String[] args) {
       try {
            String symbol = "BTC-AED";
            int depth = 20;
            String url = "https://openapi.m2.com/market-data-server/api/public/v1/orderbook?symbol=" + symbol + "&depth=" + depth;
            URL apiUrl = new URL(url);
            HttpURLConnection connection = (HttpURLConnection) apiUrl.openConnection();
            connection.setRequestMethod("GET");
            connection.setRequestProperty("Content-Type", "application/json");

            // Handle response...
            int responseCode = connection.getResponseCode();

            System.out.println("Response Code: " + responseCode);

            BufferedReader in;
            if (responseCode == HttpURLConnection.HTTP_OK) {
                in = new BufferedReader(new InputStreamReader(connection.getInputStream()));
            } else {
                in = new BufferedReader(new InputStreamReader(connection.getErrorStream()));
            }

            StringBuilder response = new StringBuilder();
            String inputLine;
            while ((inputLine = in.readLine()) != null) {
                response.append(inputLine);
            }
            in.close();

            // Print the response body
            System.out.println("Response Body: " + response.toString());
        } catch (Exception e) {
              e.printStackTrace();
        }
    }
}
// Request
const axios = require('axios');

const symbol = 'BTC-AED';
const depth = 20;
const url = `https://openapi.m2.com/market-data-server/api/public/v1/orderbook?symbol=${symbol}&depth=${depth}`;

axios.get(url)
    .then(response => {
        console.log(response.data);
    })
    .catch(error => {
        console.error('Error:', error);
    });
# Request
import requests

symbol = 'BTC-AED'
depth = 20
url = f'https://openapi.m2.com/market-data-server/api/public/v1/orderbook?symbol={symbol}&depth={depth}'

response = requests.get(url)
print("Response Code:", response.status_code)
print("Response:", response.json())

Request Fields

json field name type required description
symbol string Y symbol for which user want to see current orderbook
depth int N depth of orderbook - how many bids/asks to show. Allowed values: 1, 5, 20, 100. Default is 20

Response

// Response
{
    "symbol": "BTC-AED",
    "depth": 20,
    "transactTime": 1692249254611,
    "lastUpdateId": 1234,
    "bids": [[30000, 1], [29000, 2], [28000, 3]],
    "asks": [[31500, 1], [32000, 2], [32500, 3]]
}

Response Fields

json field name type description
symbol string symbol for which user want to see current orderbook
depth int depth of orderbook - how many bids/asks to show
transactTime long time generated by the server when it builds the response
lastUpdateId long unique ID of each load
bids decimal[][] all buy orders combined with total quantity per price, available in orderbook, sorted by descending order
asks decimal[][] all sell orders combined with total quantity per price, available in orderbook, sorted by ascending order

getL3Orderbook

Returns the full buy/sell information of the current order book, including order information

Request

# Request
curl -X GET 'https://openapi.m2.com/market-data-server/api/public/v1/l3Orderbook?symbol=BTC-AED'
// Request
import java.net.HttpURLConnection;
import java.net.URL;
import java.io.BufferedReader;
import java.io.InputStreamReader;

public class HttpRequest {
    public static void main(String[] args) {
       try {
            String symbol = "BTC-AED";
            String url = "https://openapi.m2.com/market-data-server/api/public/v1/l3Orderbook?symbol=" + symbol;
            URL apiUrl = new URL(url);
            HttpURLConnection connection = (HttpURLConnection) apiUrl.openConnection();
            connection.setRequestMethod("GET");
            connection.setRequestProperty("Content-Type", "application/json");

            // Handle response...
            int responseCode = connection.getResponseCode();

            System.out.println("Response Code: " + responseCode);

            BufferedReader in;
            if (responseCode == HttpURLConnection.HTTP_OK) {
                in = new BufferedReader(new InputStreamReader(connection.getInputStream()));
            } else {
                in = new BufferedReader(new InputStreamReader(connection.getErrorStream()));
            }

            StringBuilder response = new StringBuilder();
            String inputLine;
            while ((inputLine = in.readLine()) != null) {
                response.append(inputLine);
            }
            in.close();

            // Print the response body
            System.out.println("Response Body: " + response.toString());
        } catch (Exception e) {
              e.printStackTrace();
        }
    }
}
// Request
const axios = require('axios');

const symbol = 'BTC-AED';
const depth = 20;
const url = `https://openapi.m2.com/market-data-server/api/public/v1/l3Orderbook?symbol=${symbol}`;

axios.get(url)
    .then(response => {
        console.log(response.data);
    })
    .catch(error => {
        console.error('Error:', error);
    });
# Request
import requests

symbol = 'BTC-AED'
depth = 20
url = f'https://openapi.m2.com/market-data-server/api/public/v1/l3Orderbook?symbol={symbol}'

response = requests.get(url)
print("Response Code:", response.status_code)
print("Response:", response.json())

Request Fields

field name type required description
symbol string Y symbol for which user want to see current orderbook

Response

// Response
{
  "version": 938313,
  "transactTime": 1729735397037,
  "symbol": "BTC-AED",
  "asks": [
    ["96103.65", "1.5733698772106991", [
      ["3000000030780078", "14091", "1725245952371", "0.5733698772106991"],
      ["3000000030780080", "14092", "1725246047468", "1"]
    ]]
  ],
  "bids": [
    ["7999", "5.45", [
      ["13479", "1025970", "1729756245147", "1.25"],
      ["13473", "1025958", "1729756239114", "1.31"],
      ["8793", "1007457", "1729735418111", "1.49"],
      ["8772", "1007415", "1729735397037", "1.4"]
    ]],
    ["7998", "1.23", [
      ["4488", "1000899", "1729683192822", "1.23"]
    ]]
  ]
}

Response Fields

json field name type description
symbol string symbol for which user want to see current orderbook
transactTime long time generated by the server when it builds the response
version long orderbook version
bids array all buy orders combined with total quantity per price, available in orderbook, sorted by descending order
asks array all sell orders combined with total quantity per price, available in orderbook, sorted by ascending order
asks[0]/bids[0] string ask or bid price
asks[1]/bids[1] string ask or bid quantity(aggregate by price)
asks[2]/bids[2] array ask or bid level3 orders array(includes all order of this price)
asks[2][0]/bids[2][0] string order id
asks[2][1]/bids[2][1] string order seq(for sorting)
asks[2][2]/bids[2][2] string order time(for sorting)
asks[2][3]/bids[2][3] string order quantity

Private Endpoints

For private endpoints, we've included a few examples demonstrating signature generation. For all other endpoints, remember to include a signature to ensure successful requests.

createTestOrder

This API is identical to createNewOrder, but used for testing purposes to ensure that API is accessible, authentication works, and correct responses from API are sent. In reality, no order creation happens in the backend.

curl -X POST -H "Content-Type: application/json" -d '{"param1":"value1", "param2":"value2"}' https://openapi.m2.com/spot-api/api/private/v1/testorder
import java.net.HttpURLConnection;
import java.net.URL;
import java.io.OutputStream;
import java.io.BufferedReader;
import java.io.InputStreamReader;

public class HttpRequest {
    public static void main(String[] args) throws Exception {
        URL url = new URL("https://openapi.m2.com/spot-api/api/private/v1/testorder");
        HttpURLConnection con = (HttpURLConnection) url.openConnection();
        con.setRequestMethod("POST");
        con.setRequestProperty("Content-Type", "application/json");
        con.setDoOutput(true);

        String jsonInputString = "{\"param1\":\"value1\", \"param2\":\"value2\"}";

        try (OutputStream os = con.getOutputStream()) {
            byte[] input = jsonInputString.getBytes("utf-8");
            os.write(input, 0, input.length);
        }

        int responseCode = con.getResponseCode();
        BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream()));
        String inputLine;
        StringBuffer response = new StringBuffer();
        while ((inputLine = in.readLine()) != null) {
            response.append(inputLine);
        }
        in.close();
        System.out.println("Response Code: " + responseCode);
        System.out.println("Response: " + response.toString());
    }
}
import requests

url = "https://openapi.m2.com/spot-api/api/private/v1/testorder"
data = {"param1": "value1", "param2": "value2"}

response = requests.post(url, json=data)
print("Response Code:", response.status_code)
print("Response:", response.json())
const axios = require('axios');

const url = 'https://openapi.m2.com/spot-api/api/private/v1/testorder';
const data = {
    param1: 'value1',
    param2: 'value2'
};

axios.post(url, data, {
    headers: {
        'Content-Type': 'application/json'
    }
})
.then(response => {
    console.log(response.data);
})
.catch(error => {
    console.error('Error:', error);
});

createNewOrder

Request

// Request body
{
    "account": 1,
    "clOrdId": "abc123",
    "side": 1,
    "orderQty": 1,
    "quoteOrderQty": 3000,
    "price": 30000,
    "symbol": "BTC-AED",
    "securityId": 1,
    "ordType": 1,
    "execInst": "6",
    "timeInForce": "1"
}
curl -X POST -H "Content-Type: application/json" -d '{
    "account": 1,
    "clOrdId": "abc123",
    "side": 1,
    "orderQty": 1,
    "quoteOrderQty": 3000,
    "price": 30000,
    "symbol": "BTC-AED",
    "securityId": 1,
    "ordType": 1,
    "execInst": "6",
    "timeInForce": "1"
}' https://openapi.m2.com/spot-api/api/private/v1/order
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.io.IOException;
import java.time.Instant;

public class Main {

    private static final String API_KEY = "YOUR_API_KEY";
    private static final String SECRET = "YOUR_SECRET";

    private static final String REQUEST_METHOD = "POST";

    public static void main(String[] args) {
        try {
            String url = "https://openapi.m2.com/spot-api/api/private/v1/order";
            String timestamp = String.valueOf(System.currentTimeMillis());
            String requestBody = "{\"symbol\":\"BTC-AED\",\"ordType\":\"2\",\"orderQty\":1,\"price\":\"100\",\"side\":\"2\"}";
            String signature = generateSignature(REQUEST_METHOD, "/spot-api/api/private/v1/order", requestBody, timestamp);

            URL apiUrl = new URL(url);
            HttpURLConnection connection = (HttpURLConnection) apiUrl.openConnection();
            connection.setRequestMethod(REQUEST_METHOD);
            connection.setRequestProperty("Content-Type", "application/json");
            connection.setRequestProperty("EXCHANGE-API-TIMESTAMP", timestamp);
            connection.setRequestProperty("EXCHANGE-API-KEY", API_KEY);
            connection.setRequestProperty("EXCHANGE-API-SIGN", signature);
            connection.setDoOutput(true);

            try (OutputStream os = connection.getOutputStream()) {
                byte[] input = requestBody.getBytes(StandardCharsets.UTF_8);
                os.write(input);
            }

            int responseCode = connection.getResponseCode();
            System.out.println("Response Code: " + responseCode);


            BufferedReader in;
            if (responseCode == HttpURLConnection.HTTP_OK) {
                in = new BufferedReader(new InputStreamReader(connection.getInputStream()));
            } else {
                in = new BufferedReader(new InputStreamReader(connection.getErrorStream()));
            }

            StringBuilder response = new StringBuilder();
            String inputLine;
            while ((inputLine = in.readLine()) != null) {
                response.append(inputLine);
            }
            in.close();

            // Print the response body
            System.out.println("Response Body: " + response.toString());

        } catch (IOException | NoSuchAlgorithmException | InvalidKeyException e) {
            e.printStackTrace();
        }
    }


    private static String generateSignature(String method, String path, String params, String timestamp) throws NoSuchAlgorithmException, InvalidKeyException {
        String data = timestamp + method + path + params;
        Mac mac = Mac.getInstance("HmacSHA256");
        SecretKeySpec secretKeySpec = new SecretKeySpec(SECRET.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
        mac.init(secretKeySpec);
        byte[] hmacData = mac.doFinal(data.getBytes(StandardCharsets.UTF_8));
        // convert bytes to hex
        StringBuilder result = new StringBuilder();
        for (byte b : bytes) {
            result.append(String.format("%02x", b));
        }
        return result.toString();
    }


}
import requests
import hashlib
import hmac
import time
import json

API_KEY = "YOUR_API_KEY"
SECRET = "YOUR_API_SECRET"


def send_post_request_for_order():
    try:
        url = "https://openapi.m2.com/spot-api/api/private/v1/order"
        timestamp = str(int(time.time() * 1000))
        request_body = {
            "symbol": "BTC-AED",
            "ordType": "2",
            "orderQty": 1,
            "price": "100",
            "side": "2"
        }
        request_body_json = json.dumps(request_body)
        signature = generate_signature("POST", "/spot-api/api/private/v1/order", request_body_json, timestamp)

        headers = {
            "Content-Type": "application/json",
            "EXCHANGE-API-TIMESTAMP": timestamp,
            "EXCHANGE-API-KEY": API_KEY,
            "EXCHANGE-API-SIGN": signature
        }

        response = requests.post(url, headers=headers, data=request_body_json)
        handle_response(response)

    except Exception as e:
        print(e)


def generate_signature(method, path, params, timestamp):
    data = timestamp + method + path + params
    signature = hmac.new(SECRET.encode('utf-8'), data.encode('utf-8'), hashlib.sha256).hexdigest()
    return signature


def handle_response(response):
    print("Response Code:", response.status_code)
    print("Response Body:", response.text)


def main():
    send_post_request_for_order()


if __name__ == "__main__":
    main()

const axios = require('axios');
const crypto = require('crypto');

const API_KEY = "YOUR_API_KEY"
const SECRET = "YOUR_SECRET_KEY"

function sendPostRequestForOrder() {
    try {
        const url = "https://openapi.m2.com/spot-api/api/private/v1/order";
        const timestamp = Math.floor(Date.now());
        const requestBody = {
            symbol: "BTC-AED",
            ordType: "2",
            orderQty: 1,
            price: "100",
            side: "2"
        };
        const request_body_json = JSON.stringify(requestBody);
        const signature = generateSignature("POST", "/spot-api/api/private/v1/order", request_body_json, timestamp);

        const headers = {
            "Content-Type": "application/json",
            "EXCHANGE-API-TIMESTAMP": timestamp,
            "EXCHANGE-API-KEY": API_KEY,
            "EXCHANGE-API-SIGN": signature
        };

        axios.post(url, request_body_json, { headers })
            .then(response => handleResponse(response))
            .catch(error => console.error(error));
    } catch (error) {
        console.error(error);
    }
}

function generateSignature(method, path, params, timestamp) {
    const data = timestamp + method + path + params;
    const signature = crypto.createHmac('sha256', SECRET).update(data).digest('hex');
    return signature;
}

function handleResponse(response) {
    console.log("Response Code:", response.status);
    console.log("Response Body:", response.data);
}

function main() {
    sendPostRequestForOrder();
}

main()

Request Fields

json field name type Required Description
account int N Only 1 account(SPOT) supported
clOrdId string N Unique key per order. Should be generated & sent by the client. If empty, it's generated by the system.
side char Y 1 = Buy, 2 = Sell
orderQty decimal Y Quantity to buy/sell, decimal format like 10.52. Either orderQty or quoteOrderQty should be provided.
quoteOrderQty decimal N Quantity to buy/sell but represented in quote asset. This field is used only for market buy orders.
price decimal Y Price to buy/sell, decimal format like 150.25. Required for LIMIT order, not needed for MARKET orders.
symbol string Y Symbol to which the order belongs, like BTC-AED
securityId int N Unique internal id of particular trading pair. Either symbol or securityId should be present in the request. If both are present, validation should ensure they correspond, otherwise return a mismatch error.
ordType char Y Type of order: 1 = Market, 2 = Limit
execInst string N Execution instruction. Currently only one value = "6" is supported to initiate post-only order.
timeInForce char N Time when to execute order. Default is 1 and currently only 1 is supported to initiate Good-till-cancel (GTC)

Response

// Response body
{
    "orderId": 12345,
    "symbol": "BTC-AED",
    "clOrdId": "abc123",
    "transactTime": 1690894932589
}

Response Fields

json field name type Description
orderId long Generated by the system unique order identifier
symbol string Value that was sent during request
clOrdId string Value that was sent during request. If it was empty, a unique value would be generated by the system
transactTime long Time in UTC when order was created (this is creation time, actual order execution may be some time after even for market order)

cancelOrder

This is an async API, where we just register the request to cancel an order, but to understand if the order is cancelled or not, the user can use either websocket or getOrder API call.

// Request body
{

"account":1,
"orderId":123,
"clOrdId":"cancel_abc123",
"origClOrdId":"abc123"

}
curl -X POST -H "Content-Type: application/json" -d '{"orderId":"YOUR_ORDER_ID"}' https://openapi.m2.com/spot-api/api/private/v1/order/cancel
import java.net.HttpURLConnection;
import java.net.URL;
import java.io.OutputStream;
import java.io.BufferedReader;
import java.io.InputStreamReader;

public class HttpRequest {
    public static void main(String[] args) throws Exception {
        URL url = new URL("https://openapi.m2.com/spot-api/api/private/v1/order/cancel");
        HttpURLConnection con = (HttpURLConnection) url.openConnection();
        con.setRequestMethod("POST");
        con.setRequestProperty("Content-Type", "application/json");
        con.setDoOutput(true);

        String jsonInputString = "{\"orderId\":\"YOUR_ORDER_ID\"}";

        try (OutputStream os = con.getOutputStream()) {
            byte[] input = jsonInputString.getBytes("utf-8");
            os.write(input, 0, input.length);
        }

        int responseCode = con.getResponseCode();
        BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream()));
        String inputLine;
        StringBuffer response = new StringBuffer();
        while ((inputLine = in.readLine()) != null) {
            response.append(inputLine);
        }
        in.close();
        System.out.println("Response Code: " + responseCode);
        System.out.println("Response: " + response.toString());
    }
}
import requests

url = "https://openapi.m2.com/spot-api/api/private/v1/order/cancel"
data = {"orderId": "YOUR_ORDER_ID"}

response = requests.post(url, json=data)
print("Response Code:", response.status_code)
print("Response:", response.json())
const axios = require('axios');

const url = 'https://openapi.m2.com/spot-api/api/private/v1/order/cancel';
const data = {
    orderId: 'YOUR_ORDER_ID'
};

axios.post(url, data, {
    headers: {
        'Content-Type': 'application/json'
    }
})
.then(response => {
    console.log(response.data);
})
.catch(error => {
    console.error('Error:', error);
});

Request

json field name type required description
account int N unique ID for user account
orderId long Y Actually each request should include either orderId or origClOrdId. If both are provided then a check should be done to make sure that they match
clOrdId string N uniquely-generated client orderId (if not present would be generated by the server)
origClOrdId string N original clOrdId that was used during order creation

Response

// Response
{
"orderId":123,
"clOrdId":"cancel_abc123",
"origClOrdId":"abc123"
}
json field name type description
orderId long ID of order
clOrdId string uniquely-generated client orderId (if not present would be generated by the server)
origClOrdId string original clOrdId that was used during order creation

massCancel

Cancel all open orders for a specific trading pair like BTC-AED

Request

You can use this API to either:

json field name type required description
clOrdId string N uniquely-generated client orderId (if not present would be generated by the server)
symbol string N security to which the order is belong, like BTC-AED
securityId int N unique internal id of particular trading pair. If both are present, validation should be made to ensure that they correspond, otherwise return mismatch error
// Request body
{
    "clOrdId": "cancelall_abc123",
    "symbol": "BTC-AED",
    "securityId": 1
}
curl -X POST -H "Content-Type: application/json" -d '{
    "clOrdId": "cancelall_abc123",
    "symbol": "BTC-AED",
    "securityId": 1
}' https://openapi.m2.com/spot-api/api/private/v1/order/masscancel
import java.net.HttpURLConnection;
import java.net.URL;
import java.io.OutputStream;
import java.io.BufferedReader;
import java.io.InputStreamReader;

public class HttpRequest {
    public static void main(String[] args) throws Exception {
        URL url = new URL("https://openapi.m2.com/spot-api/api/private/v1/order/masscancel");
        HttpURLConnection con = (HttpURLConnection) url.openConnection();
        con.setRequestMethod("POST");
        con.setRequestProperty("Content-Type", "application/json");
        con.setDoOutput(true);

        String jsonInputString = "{" +
                "\"clOrdId\": \"cancelall_abc123\"," +
                "\"symbol\": \"BTC-AED\"," +
                "\"securityId\": 1" +
                "}";

        try (OutputStream os = con.getOutputStream()) {
            byte[] input = jsonInputString.getBytes("utf-8");
            os.write(input, 0, input.length);
        }

        int responseCode = con.getResponseCode();
        BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream()));
        String inputLine;
        StringBuffer response = new StringBuffer();
        while ((inputLine = in.readLine()) != null) {
            response.append(inputLine);
        }
        in.close();
        System.out.println("Response Code: " + responseCode);
        System.out.println("Response: " + response.toString());
    }
}
import requests

url = "https://openapi.m2.com/spot-api/api/private/v1/order/masscancel"
data = {
    "clOrdId": "cancelall_abc123",
    "symbol": "BTC-AED",
    "securityId": 1
}

response = requests.post(url, json=data)
print("Response Code:", response.status_code)
print("Response:", response.json())
const axios = require('axios');

const url = 'https://openapi.m2.com/spot-api/api/private/v1/order/masscancel';
const data = {
    clOrdId: 'cancelall_abc123',
    symbol: 'BTC-AED',
    securityId: 1
};

axios.post(url, data, {
    headers: {
        'Content-Type': 'application/json'
    }
})
.then(response => {
    console.log(response.data);
})
.catch(error => {
    console.error('Error:', error);
});

Response

// Response
{
    "clOrdId": "cancelall_abc123",
    "symbol": "BTC-AED",
    "securityId": 1
}
json field name type description
clOrdId string uniquely-generated client orderId
symbol string security to which the order belongs
securityId int unique internal id of the particular trading pair

getOrders

We use a single endpoint for 3 types of requests:

Request

json field name type required description
type string Y denotes what search type to use
SINGLE - get a single order by orderId/clOrdId, one of these 2 ids should be present in the request
OPEN - get only open orders (those with status new or partiallyFilled)
COMPLETED - get all orders
orderId long N used when we want to search for a single order by its orderId
clOrdId string N used when we want to search for a single order by its clOrdId - since this field is enforced to be unique only by customer, you may get many orders with the same clOrdId
symbol string N you can search all orders by a specific symbol BTC-AED
Not required for this API.
securityId int N you can search all orders by a specific symbol, passing the ID of this symbol.
If both symbol & securityId are sent, a check must be done to ensure that they match
lastRowId long N used for pagination in combination with size
you send it and the next page starts from this value
size int N used for pagination in combination with page when you want to list multiple pages
Default value: 500 and max value is 1000
startDate long N Start timestamp in UTC from which time you want to search orders. The startDate define a time slot within the last 30 days. Default is 30 days ago from the current time.
endDate long N Start timestamp in UTC from which time you want to search orders. The endDate define a time slot within the last 30 days. Default is 30 days ago from the current time.
// Request params
{
    "type": "SINGLE",
    "orderId": 123,
    "clOrdId": "abc123",
    "symbol": "BTC-AED",
    "securityId": 1,
    "lastRowId": 1,
    "size": 100,
    "filter": "side,1",
    "startDate": 1690894000000,
    "endDate": 1690894932589
}
curl -X GET 'https://openapi.m2.com/spot-api/api/private/v1/orders?type=SINGLE&orderId=123&clOrdId=abc123&symbol=BTC-AED&securityId=1&lastRowId=123&size=100&filter=side,1&startDate=1690894000000&endDate=1690894932589'
import java.net.HttpURLConnection;
import java.net.URL;
import java.io.BufferedReader;
import java.io.InputStreamReader;

public class HttpRequest {
    public static void main(String[] args) throws Exception {
        String url = "https://openapi.m2.com/spot-api/api/private/v1/orders?type=SINGLE&orderId=123&clOrdId=abc123&symbol=BTC-AED&securityId=1&lastRowId=123&size=100&filter=side,1&startDate=1690894000000&endDate=1690894932589";

        URL apiUrl = new URL(url);
        HttpURLConnection con = (HttpURLConnection) apiUrl.openConnection();
        con.setRequestMethod("GET");

        int responseCode = con.getResponseCode();
        BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream()));
        String inputLine;
        StringBuffer response = new StringBuffer();
        while ((inputLine = in.readLine()) != null) {
            response.append(inputLine);
        }
        in.close();
        System.out.println("Response Code: " + responseCode);
        System.out.println("Response: " + response.toString());
    }
}
import requests

url = "https://openapi.m2.com/spot-api/api/private/v1/orders"
params = {
    "type": "SINGLE",
    "orderId": 123,
    "clOrdId": "abc123",
    "symbol": "BTC-AED",
    "securityId": 1,
    "lastRowId": 1,
    "size": 100,
    "filter": "side,1",
    "startDate": 1690894000000,
    "endDate": 1690894932589
}

response = requests.get(url, params=params)
print("Response Code:", response.status_code)
print("Response:", response.json())
const axios = require('axios');

const url = 'https://openapi.m2.com/spot-api/api/private/v1/orders';
const params = {
    type: 'SINGLE',
    orderId: 123,
    clOrdId: 'abc123',
    symbol: 'BTC-AED',
    securityId: 1,
    lastRowId: 1,
    size: 100,
    filter: 'side,1',
    startDate: 1690894000000,
    endDate: 1690894932589
};

axios.get(url, { params })
    .then(response => {
        console.log(response.data);
    })
    .catch(error => {
        console.error('Error:', error);
    });

Response

// Response
{
    "data": List<ExecutionReportResponse>,
    "pagination": Pagination
}

ExecutionReportResponse extends ExecutionReport

{
    "rowId": 1,
    "type": "ORDER"
}

ExecutionReport

{
    "account": 1,
    "orderId": 123,
    "execId": 321,
    "clOrdId": "abc123",
    "side": 1,
    "orderQty": 1,
    "quoteOrderQty": 1000,
    "cumQty": 0.3,
    "leavesQty": 0.7,
    "price": 30000,
    "stopPx": 0,
    "avgPx": 29900,
    "lastPx": 1,
    "lastQty": 1,
    "symbol": "BTC-AED",
    "securityId": 1,
    "ordType": 1,
    "timeInForce": "1",
    "ordStatus": 1,
    "execType": 1,
    "transactTime": 1690894932589,
    "ordRejReason": 99,
    "ordRejText": "balance_insufficient",
    "ordCxlText": "self_match_prevention",
    "execInst": "6"
}

Pagination

{
    "currentPage": 1,
    "size": 200,
    "startRowId": 0,
    "lastRowId": 200
}

* For ExecutionReport, in case of reject we send rejectReason but no reason is sent for cancellation.

ExecutionReport

json field name type description
type string type of result is it order or execution
Values:
ORDER - for /orders API endpoint
TRADE - for /trades API endpoint
rowId long unique sequence number of each row used for the lastRowId request for pagination
account int since we have only 1 account now (SPOT), we can either omit it or require the user to send the userId. But in the future when we add sub-accounts, this field would come in handy
orderId long unique ID of the order
execId long unique ID of execution (one order can have multiple executions)
clOrdId string unique key per order. Should be generated & sent by the client. If empty - generated by the system like “userId-timestamp”
side char order side
Supported values:
1 = Buy
2 = Sell
orderQty decimal total quantity to buy/sell, decimal format like 10.52
quoteOrderQty decimal quantity measured in “quote” asset. Used only for market buy orders
cumQty decimal already executed quantity
leavesQty decimal not executed quantity
price decimal price to buy/sell, decimal format like 150.25
stopPx decimal decimal price, required if ordType is stop/loss
avgPx decimal average price per multiple executions (since an order can be executed multiple times with different prices, we should always calculate the average price)
lastPx decimal price of the latest execution
lastQty decimal qty of the latest execution
don’t confuse lastPx/lastQty with price/orderQty, first for the last trade where the latter for the order itself. Since an order can be partially filled and at a better price, these values
symbol string security to which the order belongs, like BTC-AED
securityId int unique internal ID of a particular trading pair. Either symbol or securityId should be present in the request. If both are present, validation should be made to ensure that they correspond, otherwise a mismatch error is returned
ordType char Type of order
Supported values:
1 = Market
2 = Limit
timeInForce char time when to execute the order
ordStatus char Last up-to-date status of the order
Supported values:
0 = New
1 = Partially filled
2 = Filled
4 = Canceled
C = Expired
8 = Reject
execType char similar to ordStatus but shows the type of the last execution
transactTime long time of the last transaction (since an order may be in different states like partially executed, filled, or canceled, each phase would have its own time, but here we should store the time of the last phase)
ordRejReason int reject reason as an int value according to FIX
ordRejText string human-readable text explanation of the reject reason
ordCxlText string reason for a cancel. In our system, we define 2 types of errors (reject, when an order doesn’t go to the orderbook and is rejected straight, and cancel - when an order gets into the orderbook but is cancelled)
execInst string Currently possible value is only “6”, which means that such an order was postOnly. This value is relevant only for ORDER. For trades, it would be null

Pagination

json field name type description
currentPage int number of the current page being returned
size int number of items per page
startRowId int start row ID for the current page
lastRowId int last row ID for the current page

getTrades

This API extends getOrders because there would be a single record per order, yet a single order can have multiple trades (for example, an order can have 2 partial fills and a last cancel - so totally 3 trades for a single order).

GET /spot-api/api/private/v1/trades

This returns paginated results if there are many records.

Request

// Request params
{
  "symbol": "BTC-AED",
  "securityId": 1,
  "lastRowId": 123,
  "size": 100
}
curl -X GET https://openapi.m2.com/spot-api/api/private/v1/trades
import java.net.HttpURLConnection;
import java.net.URL;
import java.io.BufferedReader;
import java.io.InputStreamReader;

public class HttpRequest {
    public static void main(String[] args) throws Exception {
        URL url = new URL("https://openapi.m2.com/spot-api/api/private/v1/trades");
        HttpURLConnection con = (HttpURLConnection) url.openConnection();
        con.setRequestMethod("GET");

        int responseCode = con.getResponseCode();
        BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream()));
        String inputLine;
        StringBuffer response = new StringBuffer();
        while ((inputLine = in.readLine()) != null) {
            response.append(inputLine);
        }
        in.close();
        System.out.println("Response Code: " + responseCode);
        System.out.println("Response: " + response.toString());
    }
}
import requests

url = "https://openapi.m2.com/spot-api/api/private/v1/trades"

response = requests.get(url)
print("Response Code:", response.status_code)
print("Response:", response.json())
const axios = require('axios');

const url = 'https://openapi.m2.com/spot-api/api/private/v1/trades';

axios.get(url)
    .then(response => {
        console.log(response.data);
    })
    .catch(error => {
        console.error('Error:', error);
    });

Response

ExecutionTradeReportResponse extends ExecutionReportResponse

with following additional fields:

json field name type description
fee String fee for this trade
feeAsset String fee settle asset for this trade
vat String vat for this trade
vatAsset String vat settle asset for this trade
// Response
{
  "data": List<ExecutionTradeReportResponse>,
  "pagination": Pagination
}

The response consists of two main parts:

Request

json field name type required description
symbol string N You can search all orders by a specific symbol like BTC-AED. Either symbol or securityId is required for this API.
securityId int N You can search all orders by a specific symbol, passing the ID of this symbol. If both symbol & securityId are sent, a check must be done to ensure that they match.
size int N Number of items per page. Default value: 500.
lastRowId long N Used for pagination in combination with size. You send it and the next page starts from this value.

getAccount

Here we can leave the same object as the current web-API returns.

Request

// Request params
{
  "account": 123
}
curl -X GET https://openapi.m2.com/spot-api/api/private/v1/account
import java.net.HttpURLConnection;
import java.net.URL;
import java.io.BufferedReader;
import java.io.InputStreamReader;

public class HttpRequest {
    public static void main(String[] args) throws Exception {
        URL url = new URL("https://openapi.m2.com/spot-api/api/private/v1/account");
        HttpURLConnection con = (HttpURLConnection) url.openConnection();
        con.setRequestMethod("GET");

        int responseCode = con.getResponseCode();
        BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream()));
        String inputLine;
        StringBuffer response = new StringBuffer();
        while ((inputLine = in.readLine()) != null) {
            response.append(inputLine);
        }
        in.close();
        System.out.println("Response Code: " + responseCode);
        System.out.println("Response: " + response.toString());
    }
}
import requests

url = "https://openapi.m2.com/spot-api/api/private/v1/account"

response = requests.get(url)
print("Response Code:", response.status_code)
print("Response:", response.json())
const axios = require('axios');

const url = 'https://openapi.m2.com/spot-api/api/private/v1/account';

axios.get(url)
    .then(response => {
        console.log(response.data);
    })
    .catch(error => {
        console.error('Error:', error);
    });

Response

// Response
{
  "email": "[email protected]",
  "fullname": "john doe",
  "kycStatus": "LITE",
  "canTrade": true,
  "canLogin": true,
  "canWithdraw": true,
  "makerFee": 0.0009,
  "takerFee": 0.0015
}

The response includes the following fields:

Request

json field name type required description
account int N Unique account ID. Since we have only a single account at the moment, this field can be empty.

Response

json field name type description
email string Email of the user.
fullName string Full name of the user.
kycStatus string KYC status for the user.
canLogin boolean True if user can login and False if user is not allowed to login
canTrade boolean True if user can trade.
canWithdraw boolean True if user can withdraw.
makerFee** decimal Not supported.
takerFee** decimal Not supported.

**Note: Fields with double asterisks are optional.

getPositions

GET /spot-api/api/private/v1/positions

Request

// Request params
{
  "account": 123,
  "symbol": "BTC-AED",
  "securityId": "1",
  "securityType": "ASSET",
  "accountType": "SPOT"
}
curl -X GET https://openapi.m2.com/spot-api/api/private/v1/positions
import java.net.HttpURLConnection;
import java.net.URL;
import java.io.BufferedReader;
import java.io.InputStreamReader;

public class HttpRequest {
    public static void main(String[] args) throws Exception {
        URL url = new URL("https://openapi.m2.com/spot-api/api/private/v1/positions");
        HttpURLConnection con = (HttpURLConnection) url.openConnection();
        con.setRequestMethod("GET");

        int responseCode = con.getResponseCode();
        BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream()));
        String inputLine;
        StringBuffer response = new StringBuffer();
        while ((inputLine = in.readLine()) != null) {
            response.append(inputLine);
        }
        in.close();
        System.out.println("Response Code: " + responseCode);
        System.out.println("Response: " + response.toString());
    }
}
import requests

url = "https://openapi.m2.com/spot-api/api/private/v1/positions"

response = requests.get(url)
print("Response Code:", response.status_code)
print("Response:", response.json())
const axios = require('axios');

const url = 'https://openapi.m2.com/spot-api/api/private/v1/positions';

axios.get(url)
    .then(response => {
        console.log(response.data);
    })
    .catch(error => {
        console.error('Error:', error);
    });

This request retrieves user positions.

Response

// Response
[
  {
    "symbol": "BTC",
    "securityId": 1,
    "securityType": "ASSET",
    "availableQuantity": 1,
    "lockedQuantity": 1
  }
]

The response returns an array of position objects with the following fields:

Request

json field name type required description
account int N Unique ID of the account. Until we have subaccounts, userId should be used.
symbol string N Text-based symbol, used as a filter parameter. If applied, only 1 position for this symbol would be returned.
securityId int N Either one securityId or securityType can be used for filtering position requests.
securityType string N See securityType from /instruments API.
accountType string N For which account type you want to get positions: SPOT: Default type, returns all positions for the spot account. MAIN: Returns positions for the main account.

Response

json field name type description
symbol string Text symbol of the instrument of the position.
securityId int Unique ID of the instrument.
securityType** string (Optional) See securityType from /instruments API.
availableQuantity decimal Balance available for trading.
lockedQuantity decimal Balance locked.

**Note: Fields with double asterisks are optional.

getPositions with wallet info

GET /spot-api/api/private/v1/wallet/positions

Request

// Request params
{
  "account": 123,
  "symbol": "BTC-AED",
  "securityId": 1,
  "securityType": "ASSET",
  "accountType": "SPOT",
  "walletCode": 0
}
curl -X GET https://openapi.m2.com/spot-api/api/private/v1/wallet/positions
import java.net.HttpURLConnection;
import java.net.URL;
import java.io.BufferedReader;
import java.io.InputStreamReader;

public class HttpRequest {
    public static void main(String[] args) throws Exception {
        URL url = new URL("https://openapi.m2.com/spot-api/api/private/v1/wallet/positions");
        HttpURLConnection con = (HttpURLConnection) url.openConnection();
        con.setRequestMethod("GET");

        int responseCode = con.getResponseCode();
        BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream()));
        String inputLine;
        StringBuffer response = new StringBuffer();
        while ((inputLine = in.readLine()) != null) {
            response.append(inputLine);
        }
        in.close();
        System.out.println("Response Code: " + responseCode);
        System.out.println("Response: " + response.toString());
    }
}
import requests

url = "https://openapi.m2.com/spot-api/api/private/v1/wallet/positions"

response = requests.get(url)
print("Response Code:", response.status_code)
print("Response:", response.json())
const axios = require('axios');

const url = 'https://openapi.m2.com/spot-api/api/private/v1/wallet/positions';

axios.get(url)
    .then(response => {
        console.log(response.data);
    })
    .catch(error => {
        console.error('Error:', error);
    });

This request retrieves user positions (both spot & open positions in case of perpetuals) with wallet info.

Response

// Response
[
  {
    "walletCode": 0,
    "walletName": "global_account",
    "assets": [
      {
        "symbol": "BTC",
        "securityId": 1,
        "securityType": "ASSET",
        "availableQuantity": 1,
        "lockedQuantity": 1
      }
    ]
  }
]

The response returns an array of position objects with the following fields:

Request

json field name type required description
account int N Unique ID of the account. Until we have subaccounts, userId should be used.
symbol string N Text-based symbol, used as a filter parameter. If applied, only 1 position for this symbol would be returned.
securityId int N Either one securityId or securityType can be used for filtering position requests.
securityType string N See securityType from /instruments API.
accountType string N For which account type you want to get positions: SPOT: Default type, returns all positions for the spot account. MAIN: Returns positions for the main account.
walletCode int N For which wallet you want to get positions: 0:global_account. 1:uae_account

Response

json field name type description
walletCode int Wallet code, 0:global_account. 1:uae_account.
walletName string Text wallet of the instrument of the position.
symbol string Text symbol of the instrument of the position.
securityId int Unique ID of the instrument.
securityType** string (Optional) See securityType from /instruments API.
availableQuantity decimal Balance available for trading.
lockedQuantity decimal Balance locked.

**Note: Fields with double asterisks are optional.

WebSocket API

WebSocket API designed to provide real-time data streaming for enhanced trading experiences. This API offers seamless integration with our existing infrastructure and supports various functionalities.

Usage Areas

The WebSocket API primarily focuses on two key areas:

Heartbeat Functionality

To maintain a stable connection, clients are required to send a heartbeat message at least once every three minutes. Failure to do so will result in the closure of the connection. Here's the format for the heartbeat message:

{"id":"unique_id","type":"PING"}

For each ping message, a corresponding pong message will be received.

Authentication

Authentication within the WebSocket API is seamlessly integrated. Users need to provide the following parameters for successful authorization:

Error Codes

The WebSocket API returns specific error codes for different scenarios:

Public Endpoints

Orderbook Updates

Provides full snapshot updates of the orderbook.

Subscription Request

// Orderbook Updates Request
{"method":"SUBSCRIBE","id":"unique_id","params":["spot_depth5@BTC-AED"]}

use params to subscrip depth you want, support depth: 5 10 20

Orderbook Updates - Real-time

Provides real-time depth data flow updates of the orderbook.

Subscription Request:

// Orderbook Updates - Real-time Request
{"method":"SUBSCRIBE","id":"unique_id","params":["spot_depth@BTC-AED"]}

Response

// Response 
{
  "id": "9e8ce974d81d488da4aaf8fc94e39e45",
  "type": "MESSAGE",
  "topic": "spot_depth",
  "subject": "BTC-AED",
  "sn": 0,
  "body": {
    "e": "depthUpdate",
    "E": 1718358058983,
    "s": "BTC-AED",
    "U": 657,
    "u": 659,
    "a": [["3","2.005"]
    ],
    "b": [["10","2.002"]
    ]
  }
}

Calibration procedure:

  1. After receiving the websocket depth data flow, cache the data.
  2. Initiate a depth request to get the depth snapshot data of order, Rest api is: /market-data-server/api/public/v1/orderbook?symbol=BTC-AED&depth=20, Both u and U initial value is: lastUpdateId.
  3. Playback the cached depth data flow to depth snapshot data.
  4. Apply the new depth data flow to the local depth snapshot, we need to ensure that U(new)<=u+1(old) and u(new) > u(old). Update the local depth snapshot data based on u/U according to the price and size. If the size=0, update the u/U and remove the price of which the size is 0 out of depth. For other cases, please update the price.

The Change attribute of depth data is a string value of "price,size", namely: ["price", "size"].

Please note: 1. size refers to the latest size corresponding to price. When the size is 0, the corresponding price needs to be deleted from the order book.

  1. the rest depth:/market-data-server/api/public/v1/orderbook?symbol=BTC-AED&depth=500 param "depth" maximum is 500, it's means if the receiving websocket depth data flow price may not in 500 level orderbook, but you still need to update the u/U of local depth snapshot data, and out range price you can choose loss it.

L3 Orderbook Updates - Real-time

Provides real-time level3 depth data flow updates of the orderbook.

Subscription Request:

// Orderbook Updates - Real-time Request
{"method":"SUBSCRIBE","id":"unique_id","params":["spot_depth_l3@BTC-AED"]}

Response

// Response 
{
  "id": "2068a92fa9734dae9734105f0518ba26",
  "type": "MESSAGE",
  "topic": "spot_depth_l3",
  "subject": "BTC-AED",
  "sn": 0,
  "body": {
    "startVersion": 917659,
    "endVersion": 917659,
    "symbol": "BTC-AED",
    "asks": [],
    "bids": [
      [
        "7951",
        "2.61",
        [
          [
            "11771",
            "1019191",
            "1729748594947",
            "0.0"
          ]
        ]
      ]
    ],
    "midPrice": "7951"
  }
}

Calibration procedure:

  1. After receiving the websocket depth data flow, cache the data.
  2. Initiate a full orders snapshot request to get the full orders snapshot data
  3. Playback the cached increment orders data flow to full orders snapshot data.
  4. Apply the new increment orders data flow to the local full orders snapshot, we need to ensure that version start(new)<=version end(old)+1 and version end(new) > version end(old). Update the local full orders snapshot data based on version start/version end according to the orderId and size. If the size=0, update the version start/version end and remove the orderId of which the size is 0 out of orderbook. For other cases, please update the order(size).

Please note: 1. size refers to the latest size corresponding to orderid. When the size is 0, the corresponding orderid needs to be deleted from the order book. 2. orders sorting rules: order time + seq 3. websocket may loss data,so if version discontinuous,client need to rebuild order book,rebuild way: clear local orderbook and redo: Calibration procedure.

Trade Updates

Delivers real-time updates on trades.

Subscription Request:

// Trade Updates Request
{"method":"SUBSCRIBE","id":"unique_id","params":["spot_trade@BTC-AED"]}

Private Endpoints

User Positions

Notifies about any changes in user positions, including deposits, withdrawals, or trades.

Subscription Request:

// User Positions Request
{"method":"SUBSCRIBE","id":"unique_id","params":["api_user_positions@all"]}

Response

// User Positions Response
{
  "id": "80c034223ee34f4e8ec01c871b65aaff",
  "type": "MESSAGE",
  "topic": "api_user_positions",
  "subject": "all",
  "sn": 0,
  "body": {
    "symbol": "BTC",
    "securityId": 0,
    "securityType": 0,
    "availableQuantity": 993,
    "lockedQuantity": 3
  }
}

Response Fields

Field Name Type Description
symbol string Unique symbol representing a trading pair.
securityId int unique integer ID of instrument
securityType string type of position (used for filtering)
availableQuantity decimal Balance available for trading.
lockedQuantity decimal Balance locked.

Notifies about any changes in user positions, including deposits, withdrawals, or trades.

Subscription Request:

{"method":"SUBSCRIBE","id":"unique_id","params":["COMMON@ACCOUNT"]}

Response

{
  "id": "9cd3a3110aa74a89a4a4cd899d4a9dbc",
  "type": "MESSAGE",
  "topic": "COMMON",
  "subject": "ACCOUNT",
  "sn": 0,
  "body": {
    "e": "balanceUpdate", // event type
    "E": 1718796759745, // event time
    "a": "BTC", // asset
    "d": "100", // balance change
    "T": 1718796759745, // clear time
    "f": "10", // available balance
    "l": "0", // locked balance
    "w": 0 // wallet code, 0:global_account 1:uae_account
  }
}

Response Fields

Field Name Type Description
e string Event type.
E long Event time.
a string Asset
d string Balance change.
f string Available balance.
l string Locked balance.
T long Clear time.
w int Wallet code, 0:global_account 1:uae_account.

User Orders

Provides updates on all order interactions and cancel reject notifications.

Subscription Request

// User Orders Request
{"method":"SUBSCRIBE","id":"unique_id","params":["api_user_orders@all"]}

ExecutionReport Response

the normal execution report response

// ExecutionReport Response 
{
  "id": "35e7fad4297c4fddae1d51bee2a03add",
  "type": "MESSAGE",
  "topic": "api_user_orders",
  "subject": "all",
  "sn": 0,
  "body": {
    "account": 99313426,
    "orderId": 2000000011890072,
    "execId": 0,
    "clOrdId": "api_99313426_1697888777740",
    "side": "2",
    "orderQty": 1,
    "quoteOrderQty": 0,
    "cumQty": 0,
    "leavesQty": 1,
    "price": 100,
    "lastPx": 0,
    "symbol": "BTC-AED",
    "securityId": 3,
    "ordType": "2",
    "timeInForce": "1",
    "ordStatus": "0",
    "execType": "0",
    "transactTime": 1697888781022,
    "ordRejReason": 0,
    "e": "executionReport"
  }
}

Response Fields

Field Name Type Description
account string Unique ID of the account. Until we have subaccounts, userId should be used.
orderId int Generated by the system unique order identifier
execId string unique ID of execution (one order can have multiple executions)
clOrdId string unique key per order. Should be generated & sent by the client. If empty - generated by the system like “userId-timestamp”
side char order side
Supported values:
1 = Buy
2 = Sell
orderQty decimal total quantity to buy/sell, decimal format like 10.52
quoteOrderQty decimal quantity measured in “quote” asset. Used only for market buy orders
cumQty decimal already executed quantity
leavesQty decimal not executed quantity
price decimal price to buy/sell, decimal format like 150.25
lastPx decimal price of the latest execution
lastQty decimal qty of the latest execution
don’t confuse lastPx/lastQty with price/orderQty, first for the last trade where the latter for the order itself. Since an order can be partially filled and at a better price, these values
symbol string security to which the order belongs, like BTC-AED
securityId int unique internal ID of a particular trading pair. Either symbol or securityId should be present in the request. If both are present, validation should be made to ensure that they correspond, otherwise a mismatch error is returned
ordType char Type of order
Supported values:
1 = Market
2 = Limit
timeInForce char time when to execute the order
ordStatus char Last up-to-date status of the order
Supported values:
0 = New
1 = Partially filled
2 = Filled
4 = Canceled
C = Expired
8 = Reject
execType char similar to ordStatus but shows the type of the last execution
transactTime long time of the last transaction (since an order may be in different states like partially executed, filled, or canceled, each phase would have its own time, but here we should store the time of the last phase)
ordRejReason int reject reason as an int value according to FIX
ordRejText string human-readable text explanation of the reject reason
ordCxlText string reason for a cancel. In our system, we define 2 types of errors (reject, when an order doesn’t go to the orderbook and is rejected straight, and cancel - when an order gets into the orderbook but is cancelled)

OrderCancelReject Response

order cancel or reject response

// OrderCancelReject Response 
{
  "id": "35e7fad4297c4fddae1d51bee2a03add",
  "type": "MESSAGE",
  "topic": "api_user_orders",
  "subject": "all",
  "sn": 0,
  "body": {
    "account": 123,
    "orderId": 123,
    "clOrdId": "abc",
    "origClOrdId": "abc",
    "cxlRejReason": 1,
    "text": "failed",
    "e": "executionReport"
  }
}

Response Fields

Field Name Type Description
account string Unique ID of the account. Until we have subaccounts, userId should be used.
orderId int Generated by the system unique order identifier
clOrdId string Unique key per order. Should be generated & sent by the client. If empty, it's generated by the system.
cxlRejReason int 1:order not found in match-engie 2:other reason
text string system cancel reason message

Example Connection

Here's an example demonstrating how to connect to private WebSocket channels using HMAC SHA256 signature:

API_KEY=YOUR_API_KEY
SECRET=YOUR_SECRET_KEY
SIG=hmac(API_KEY + timestamp, SECRET)

wscat -c "wss://stream.m2exchange.com/endpoint?apikey=$API_KEY&timestamp=YOUR_TIMESTAMP&signature=$SIG"

Upon successful connection, you will receive a welcome message. You can then subscribe to the desired topics for real-time updates:

{"method":"SUBSCRIBE","id":"123","params":["spot_depth5@BTC-AED","spot_trade@BTC-AED","api_user_orders@all","api_user_positions@all"]}

For each subscription, you will receive an acknowledgment message (ACK).

Errors

Definitions errors in the REST API and Websocket is as follow

REST API Errors

There are 3 types of orders:

JSON Body Example

4xx errors

{
    "code": "100200",
    "msg": "business error",
    "userMsg": "Instrument not found: symbol=BNB-AED"
}

200 errors

{
    "code": "500",
    "msg": "symbol_not_open",
    "data": null
}

List of 4xx errors:

HTTP Status userMsg error explanation
400 Max allowed length for clOrdId=36 Max size that string for clOrdId field can be is 36 characters
400 Invalid field for market buy order: orderQty When you send market buy order you should include quantity in quote asset with a field quoteOrderQty. For example for BTC/USDT pair if you want to buy bitcoin for 1000usdt you should add quoteOrderQty=1000
400 Field missing for market buy order: quoteOrderQty When you send market buy order, field quoteOrderQty should be provided with the request body
400 Field missing: orderQty When you send market sell order, field orderQty should be provided with the request body
400 Invalid field for market order: price When you send market order, you shouldn’t include field price into request body
400 Invalid pair When you send request to create new order, you can only send it for pairs with valid symbol. You can check the pair when you fetch instruments
400 Instrument is not a pair: securityType=ASSET You should send request to a correct trading pair. If you send request with symbol=USDT you will get this error, cause USDT is ASSET not a PAIR
400 Instrument not found: symbol=BTC-ABC If you send request to a PAIR that doesn’t exist (you can take a look at all existing pairs using instruments API), you will get this error, cause we don’t have pair BTC-ABC

List of 200 errors (code would always be 500, and data would always be null):

msg Error Explanation
1 symbol_not_open The trade pair is not allowed to trade; Please contact tech support team.
2 user_not_found User session not found, you can try to log in again.
3 user_kyc_not_passed You have to be KYC verified. If you do believe you have a valid KYC verified, please contact CX team.
4 user_invalid Your profile status is identified as invalid. Please contact CX team.
5 no_trade_permission You have no permission to place an order. Please contact CX team.
6 side_is_required Order “side” is required, either buy or sell.
7 symbol_is_required Order symbol is required.
8 type_is_required Order type is required.
9 limit_order_price_is_required For a limit order, you must specify the price.
10 limit_order_price_should_positive For a limit order, the price must be positive.
11 limit_order_quantity_is_required For a limit order, you must specify the quantity.
12 limit_order_quantity_should_positive For a limit order, the quantity must be positive.
13 not_support_order_type Order type is not supported. We only support order types: LIMIT, MARKET.
14 clientOid_invalid For open-api in the web/app should just ignore.
15 market_order_need_no_price The market order should NOT have a price.
16 buy_order_amount_is_required The market buy order must have an amount of quote.
17 buy_order_amount_should_be_positive The market buy order amount of quote must be positive.
18 sell_order_quantity_is_required The market sell order must have a quantity of base pass through.
19 buy_order_need_not_quantity The market buy order must have a quantity of base pass through.
20 market_order_should_not_provide_both_quantity_amount The market order can only have one quantity (base) and amount (quote).
21 market_order_should_provide_quantity_or_amount The market order must have one quantity (base) and amount (quote).
22 order_quantity_should_be_positive Order quantity cannot be negative.
23 invalid_amount_precision Invalid order amount (quote) precision.
24 quantity_too_small Order quantity (base) is too small.
25 invalid_quantity_precision Invalid order quantity (base) precision.
26 invalid_min_price_precision Invalid precision of price.
27 order_min_price_should_zero_or_positive Min price precision should be no less than 0.
28 minQty_too_small MinQty is too small.
29 amount_should_be_positive Amount should be positive if present.
30 quantity_should_be_positive Quantity should be positive if present.
31 order_max_price_should_zero_or_positive Max price should be no less than 0.
32 order_should_provide_either_quantity_or_amount Order should have either Price of Base OR Amount of Quote.
33 order_should_not_null_both_quantity_and_amount Order must have have either Price of Base OR Amount of Quote, but can not have both be null.
34 market_order_should_provide_quantity_or_amount Market Order should have either Price of Base OR Amount of Quote.
35 market_order_should_not_provide_both_quantity_amount Market Order should not have both Price of Base and Amount of Quote.
36 market_order_need_no_price For market orders, there should be no price pass through.
37 balance_insufficient User balance is insufficient.
38 quantity_too_small The order quantity is too small.
39 quantity_not_match_increment The price incremental is incorrect.
40 amount_too_small The order amount is too small.
41 amount_not_match_increment The order amount incremental is incorrect.
42 request_timeout Request timeout.
43 exceed_user_max_limit The user’s number of open order reaches the maximum cap of one user.
44 exceed_orderbook_max_limit The number of trades in the order book reaches the match engine maximum cap。
45 orderId_duplicated Duplicate order.
46 market_order_not_support_cancel User can't cancel a market order.
47 trade_paused The trade was paused trading.
48 price_can_not_match Price band issue
49 trade_cancel_only The pair status is "cancel only," which means the user can only cancel their orders and cannot place any new orders for this pair.
50 symbol_not_open The pair is not available for trade.
51 coin_not_found The quote or base coin is not valid in the exchange.
52 user_symbol_entities_not_match The user’s entity doesn’t match the pair’s entity. It may be Bahamas user wanna trade ADGM pair, which is not legalized.
53 no_trading_allowed The pair doesn’t support this order.
54 pair_not_found The specified pair was not found.
55 user_entity_region_not_found Can't find the user’s entity region, need to contact CX team to verify KYC status.
56 pair_entity_not_found Can't find the pair’s entity, which means the entity is missing for the pair config. Need contact tech support team.

Websocket Errors

Such errors returned through websocket with status=rejected


Feel free to reach out to support if you have any questions or need further assistance!