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:
- 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
). - Hash the concatenated string with the SHA256 algorithm and then sign the resulting hash using the Ed25519 standard.
- 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----- |
Method | POST |
URL | https://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 key | cc793443b363e36ce89ebf1a300aa012ac05d19be38008281a3087d8028ec6a3 |
Signature | dcfd6fb31c0c4503d8feeb01fd12996e66a99fcc1393e19f204fdf7e65fb49f9f6364a227f32a8ac3acbe644eb0fb3dc8806688614e14a5a8e4ea37e45c01e04 |
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.