To guarantee the safety and security of API usage, the CVEX API requires the use of API keys for all public requests, along with asymmetric signatures for trading-related API interactions.

To start using CVEX API contact CVEX Team in order to obtain your keys: [email protected]

  • API Key: hex-encoded public key authenticates all your API requests.
  • Secret Key: private key is used for signing trading requests.

❗️

Important

Securely save your secret key during the key generation process. It is not stored on our systems and cannot be retrieved later. Loss of the secret key means you will be unable to use the Trading API with the associated API key.

API Key Usage

Include a valid API key as the X-API-Key HTTP header in each request to the CVEX API:

GET /api/resource
X-API-Key: YOUR_API_KEY

Authentication is also required when opening a WebSocket connection:

const socket = new WebSocket('wss://ws.cvex.trade/ws/v1?api_key=YOUR_API_KEY');

If authentication fails, the CVEX API will return a 401 Unauthorized status code:

{
  "status": "error",
  "code": 401,
  "message": "Authentication failed: Invalid API key"
}

Signing Trading Requests

Every trading request through the CVEX API must be digitally signed using the secret key associated with your API key.

To generate a signature for a trading request, follow these steps:

  1. Before signing, the singed message must be properly formatted. Concatenate the HTTP method and URL, separated by a space, followed by the request body, separated by a newline (\n).
  2. Hash the concatenated string with the SHA256 algorithm and then sign the resulting hash using the Ed25519 standard.
  3. Include the hex-encoded signature in the X-Signature header of your HTTP request.

Here's how the components of the message are concatenated and then hashed and signed::

METHOD URL
REQUESTBODY

For instance, a POST request to https://api.cvex.trade/v1/trading/order with a JSON body of {"orderType":"buy","amount":"100"} would appear as:

POST https://api.cvex.trade/v1/trading/order
{"timestamp":1723802442080,"orderType":"buy","amount":"100"}

Include the signature in a trading request as follows:

POST https://api.cvex.trade/v1/trading/order
X-API-Key: YOUR_API_KEY
X-Signature: GENERATED_SIGNATURE

If the signature is incorrect or missing, the server will return a 401 Unauthorized status code:

{
  "status": "error",
  "code": 401,
  "message": "Authentication failed: Invalid signature"
}

Canonical implementations

To simplify integration with the CVEX API, we provide a canonical implementation of signature generation:

import hashlib
import json
import time
import requests
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.serialization import Encoding, PublicFormat

API_PRIVATE_KEY_PATH = "private_key.pem"

MAKE_ORDER_URL = "https://api.cvex.trade/v1/trading/order"
HTTP_METHOD = "POST"
MAKE_ORDER_BODY = {
    "contract": "1",
    "limit_price": "57777",
    "quantity_steps": "1000",
    "reduce_only": False,
    "time_in_force": "GTC",
    "type": "limit",
    "timestamp": 0,
    "recv_window": 60000
}

# timestamp value will be replaced with a current ts below

def load_private_key(file_path):
    """Load and return the Ed25519 private key."""
    with open(file_path, "rb") as pem_file:
        return serialization.load_pem_private_key(pem_file.read(), password=None)

def create_headers(private_key, message):
    """Create headers with API key and signature."""
    pub_key = private_key.public_key().public_bytes(encoding=Encoding.Raw, format=PublicFormat.Raw).hex()
    hash_digest = hashlib.sha256(message.encode()).digest()
    # Sign the message using Ed25519
    signature_hex = private_key.sign(hash_digest).hex()
    return {"X-API-KEY": pub_key, "X-Signature": signature_hex}

if __name__ == "__main__":
    key = load_private_key(API_PRIVATE_KEY_PATH)

    MAKE_ORDER_BODY["timestamp"] = int(time.time() * 1000)
    message_for_signing = f"{HTTP_METHOD} {MAKE_ORDER_URL}\n{json.dumps(MAKE_ORDER_BODY)}"

    headers = create_headers(key, message_for_signing)
    response = requests.post(MAKE_ORDER_URL, json=MAKE_ORDER_BODY, headers=headers)

    if response.status_code >= 300:
        raise Exception(f"Request failed with status {response.status_code}: {response.text}")

    print(response.text)
import fs from 'fs';
import {
    createPublicKey,
    createPrivateKey,
    sign,
    createHash
} from 'crypto';

const API_PRIVATE_KEY_PATH = 'private_key.pem';
const API_BASE_URL = 'https://api.cvex.trade';

const MAKE_ORDER_URL = `${API_BASE_URL}/v1/trading/order`;
const HTTP_METHOD = 'POST';

/**
 * Constructs the order body as a JSON string.
 * @param {Object} params - Contains the order timestamp.
 * @returns {string} - JSON string for the order body.
 */
const makeOrderBody = ({ timestamp }) => JSON.stringify({
    contract: '1',
    customer_order_id: '123id71',
    limit_price: '61494',
    quantity_steps: '5',
    reduce_only: false,
    time_in_force: 'GTC',  // Good Till Cancelled
    type: 'limit',
    timestamp,
    recv_window: 60000
});

/**
 * Loads the private key from a specified file path.
 * @param {string} filePath - The path to the PEM file.
 * @returns {Object|null} - Returns the private key object or null if loading fails.
 */
const loadPrivateKey = (filePath) => {
    try {
        const pem = fs.readFileSync(filePath, 'utf8');
        return createPrivateKey({
            key: pem,
            format: 'pem',
            type: 'pkcs8',
        });
    } catch (error) {
        console.error('Error loading private key:', error.message);
        return null;
    }
};

/**
 * Extracts the API key from the private key by processing the DER public key.
 * @param {Object} privateKey - The private key object.
 * @returns {string} - The extracted API key in hexadecimal format.
 */
const getApiKey = (privateKey) => {
    const derPublicKey = createPublicKey(privateKey).export({ format: 'der', type: 'spki' });
    const rawPublicKey = derPublicKey.subarray(-32); // Extract the last 32 bytes (raw key)
    return rawPublicKey.toString('hex');
};

/**
 * Constructs the message to be signed by combining HTTP method, URL, and body.
 * @param {string} method - The HTTP method (e.g., POST).
 * @param {string} url - The request URL.
 * @param {string} body - The request body.
 * @returns {string} - The message to be signed.
 */
const getMessageForSigning = (method, url, body) => {
    return `${method} ${url}\n${body}`;
};

/**
 * Signs a message using the provided private key.
 * @param {Object} privateKey - The private key object.
 * @param {Buffer} message - The message hash to be signed.
 * @returns {string} - The generated signature in hexadecimal format.
 */
const signMessage = (privateKey, message) => {
    return sign(null, message, privateKey).toString('hex');
};

/**
 * Main function to handle the order creation flow.
 */
async function main() {
    const privateKey = loadPrivateKey(API_PRIVATE_KEY_PATH);
    if (!privateKey) {
        console.error('Private key is required to proceed.');
        return;
    }

    const apiKey = getApiKey(privateKey);
    const timestamp = Date.now();
    const bodyWithTimestamp = makeOrderBody({ timestamp });

    const messageForSigning = getMessageForSigning(HTTP_METHOD, MAKE_ORDER_URL, bodyWithTimestamp);
    const hash = createHash('sha256').update(messageForSigning).digest();

    const signatureHex = signMessage(privateKey, hash);

    const headers = {
        'Content-Type': 'application/json',
        'X-API-KEY': apiKey,
        'X-Signature': signatureHex,
    };

    try {
        const response = await fetch(MAKE_ORDER_URL, {
            method: HTTP_METHOD,
            headers,
            body: bodyWithTimestamp
        });

        const data = await response.json();

        if (data.error) {
            console.error('Order creation failed:', data.error);
        } else {
            console.log('Order created successfully:', data);
        }
    } catch (error) {
        console.error('Order request failed:', error.message);
    }
}

// Execute the main function
main();
package main

import (
	"bytes"
	"crypto/ed25519"
	"crypto/sha256"
	"crypto/x509"
	"encoding/hex"
	"encoding/pem"
	"fmt"
	"io"
	"log"
	"net/http"
	"os"
	"time"
)

const apiPrivateKeyPath = "private_key.pem"

const makeOrderURL = "https://api.cvex.trade/v1/trading/order"
const httpMethod = `POST`
const makeOrderBody = `{
  "contract": "1",
  "customer_order_id": "123id7",
  "limit_price": "61494",
  "quantity_steps": "5",
  "reduce_only": false,
  "time_in_force": "GTC",
  "type": "limit",
  "timestamp": %d,
  "recv_window": 60000
}`

func main() {
	pcs8PrivateKey, err := os.ReadFile(apiPrivateKeyPath)
	if err != nil {
		log.Fatal(err)
	}

	rawPrivateKey := parsePrivateKey(pcs8PrivateKey)
	pubKey := rawPrivateKey.Public()
	apiKey := hex.EncodeToString(pubKey.(ed25519.PublicKey))

	bodyWithTimestamp := fmt.Sprintf(makeOrderBody, time.Now().UnixMilli())
	messageForSigning := fmt.Sprintf("%s %s\n%s", httpMethod, makeOrderURL, bodyWithTimestamp)

	hash := sha256.New()

	_, err = hash.Write([]byte(messageForSigning))
	if err != nil {
		log.Fatal(err)
	}

	signedMessage := ed25519.Sign(rawPrivateKey, hash.Sum(nil))
	signatureHex := hex.EncodeToString(signedMessage)
	bodyReader := bytes.NewReader([]byte(bodyWithTimestamp))

	r, err := http.NewRequest(httpMethod, makeOrderURL, bodyReader)
	if err != nil {
		log.Fatal(err)
	}

	r.Header.Add("Content-Type", "application/json")
	r.Header.Add("X-API-KEY", apiKey)
	r.Header.Add("X-Signature", signatureHex)

	httpClient := http.DefaultClient
	resp, err := httpClient.Do(r)
	if err != nil {
		log.Fatal(err)
	}

	readBody, err := io.ReadAll(resp.Body)
	if err != nil {
		log.Fatal(err)
	}

	if resp.StatusCode > 299 {
		log.Fatalf("request is failed %d: %s", resp.StatusCode, string(readBody))
	}

	log.Println(string(readBody))
}

func parsePrivateKey(pemPrivateKey []byte) ed25519.PrivateKey {
	// Decode PEM-encoded private key
	block, _ := pem.Decode(pemPrivateKey)
	if block == nil {
		log.Fatal("failed to decode PEM block containing private key")
	}

	// Parse the DER-encoded private key
	key, err := x509.ParsePKCS8PrivateKey(block.Bytes)
	if err != nil {
		log.Fatal(err)
	}

	// Convert the parsed private key to Ed25519
	ed25519PrivateKey, ok := key.(ed25519.PrivateKey)
	if !ok {
		log.Fatal("parsed key is not an Ed25519 private key")
	}

	return ed25519PrivateKey
}

Canonical example

Below, we provide a test key pair along with example input parameters and their expected outcomes to assist you in verifying the accuracy of your signature generation implementation.

API key is a public part of your trading key pair.

Secret key-----BEGIN PRIVATE KEY-----
MC4CAQAwBQYDK2VwBCIEIMbphXkpR5gNTghgn3zr+NzW0lrzZ9BU8SCvW2yjM6WK
-----END PRIVATE KEY-----
MethodPOST
URLhttps://api.cvex.trade/v1/trading/order
Body{"id":"21002","customer_order_id":"123id7","limit_price":"61493","quantity_steps":"3","recv_window":60000,"reduce_only":false,"time_in_force":"GTC","timestamp":1725539413265,"type":"limit"}
API keycc793443b363e36ce89ebf1a300aa012ac05d19be38008281a3087d8028ec6a3
Signaturedcfd6fb31c0c4503d8feeb01fd12996e66a99fcc1393e19f204fdf7e65fb49f9f6364a227f32a8ac3acbe644eb0fb3dc8806688614e14a5a8e4ea37e45c01e04

Security Consideration

Both the API key and the secret key must be treated with the highest level of security. The API key serves as a shared secret between your application and the CVEX API service, whereas the secret key holds even greater importance as it is known solely by your application to sign trading requests. Unauthorized access to the secret key could enable an attacker to conduct fraudulent transactions.

Limit access to your keys strictly to those parts of your application that require it, and to developers who need these credentials for their work. Employ the principle of least privilege to minimize exposure of sensitive data. Utilize encrypted storage solutions to secure your keys; encryption at rest helps prevent unauthorized access and protects your keys in the event of a data breach.

Should you suspect a secret key leak, immediately revoke the corresponding API key on the My API Keys page and generate a new pair to continue operations safely.