All API requests must be authenticated using HMAC-SHA512 signatures. This protects against man-in-the-middle attacks by never transmitting your API secret over the network.
Authentication
Security WarningNever use production API keys in online code compilers or shared environments. Always generate signatures in a secure, controlled environment.
How Signature Authentication Works
- You generate a message string from request components (path + body)
- You sign this message using your API secret and HMAC-SHA512
- You send the signature in request headers (not the secret)
- Zodia Markets verifies the signature using your registered API key
Required Headers
All authenticated requests must include these headers:
Rest-Key: <your_api_key>
Rest-Sign: <generated_signature>
Generating Signatures
Core Signature Function Example
import hmac
import hashlib
import base64
def generate_signature(secret, message):
"""Generate HMAC-SHA512 signature"""
secret_bytes = base64.b64decode(secret)
signature = hmac.new(
secret_bytes,
message.encode('utf-8'),
digestmod=hashlib.sha512
).digest()
return base64.b64encode(signature).decode('utf-8')const crypto = require('crypto');
function generateSignature(secret, message) {
const secretBytes = Buffer.from(secret, 'base64');
const signature = crypto
.createHmac('sha512', secretBytes)
.update(message)
.digest('base64');
return signature;
}API Signature Generation
Message Format:
path + '\0' + body_json
The \0 is a null byte separator between path and body.
import time
import json
def generate_api_signature(secret, path, body_dict):
"""Generate API signature"""
# Add tonce to body
body_dict['tonce'] = int(time.time() * 1000000) # Microseconds
body_json = json.dumps(body_dict)
# Build message: path + null byte + body
message = path + '\0' + body_json
return generate_signature(secret, message), body_jsonfunction generateApiSignature(secret, path, bodyDict) {
// Add tonce to body
bodyDict.tonce = Date.now() * 1000; // Microseconds
const bodyJson = JSON.stringify(bodyDict);
// Build message: path + null byte + body
const message = path + '\0' + bodyJson;
return {
signature: generateSignature(secret, message),
body: bodyJson
};
}const moment = require('moment');
const CryptoJS = require('crypto-js');
const path = 'api/3/transaction/list';
// Request payload
const requestBodyObj = {
transactionClass: 'RFSTRADE',
accountGroupUuid: pm.variables.get('account_group_uuid')
};
// Add tonce
requestBodyObj.tonce = moment().valueOf() * 1000; // Microseconds
const requestBodyString = JSON.stringify(requestBodyObj);
// Generate signature
const secret = CryptoJS.enc.Base64.parse(pm.variables.get('Rest-Secret'));
const hmac = CryptoJS.algo.HMAC.create(CryptoJS.algo.SHA512, secret);
hmac.update(path + '\0' + requestBodyString);
// Set Postman variables for use in request
pm.variables.set('Rest-Sign', CryptoJS.enc.Base64.stringify(hmac.finalize()));
pm.variables.set('postBody', requestBodyString);Tonce is the Timestamp in microseconds (1/1,000,000 second). Prevents replay attacks and must be increasing for each request. Included in request body, not headers
Making Authenticated Requests
Complete Request Example
import requests
import json
import time
BASE_URL = 'https://trade-uk.sandbox.zodiamarkets.com'
API_KEY = '<your_api_key>'
API_SECRET = '<your_api_secret>'
def make_api_request(method, path, body=None):
"""Make authenticated API request"""
body = body or {}
# Generate signature
signature, body_json = generate_api_signature(API_SECRET, path, body)
# Build headers
headers = {
'Rest-Key': API_KEY,
'Rest-Sign': signature,
'Content-Type': 'application/json'
}
# Make request
url = BASE_URL + '/' + path
response = requests.request(method, url, headers=headers, data=body_json)
return response.json()
# Example: Get account info
account = make_api_request('POST', 'api/3/account')
print(account)const https = require('https');
const BASE_URL = 'trade-uk.sandbox.zodiamarkets.com';
const API_KEY = '<your_api_key>';
const API_SECRET = '<your_api_secret>';
function makeApiRequest(method, path, body = {}) {
return new Promise((resolve, reject) => {
// Generate signature
const { signature, body: bodyJson } = generateApiSignature(
API_SECRET,
path,
body
);
// Build headers
const headers = {
'Rest-Key': API_KEY,
'Rest-Sign': signature,
'Content-Type': 'application/json',
'Content-Length': Buffer.byteLength(bodyJson)
};
// Make request
const options = {
hostname: BASE_URL,
path: '/' + path,
method: method,
headers: headers
};
const req = https.request(options, (res) => {
let data = '';
res.on('data', chunk => data += chunk);
res.on('end', () => resolve(JSON.parse(data)));
});
req.on('error', reject);
req.write(bodyJson);
req.end();
});
}
// Example: Get account info
makeApiRequest('POST', 'api/3/account')
.then(account => console.log(account));Common Request Examples
Get Account Information:
account = make_api_request('POST', 'api/3/account', {})makeApiRequest('POST', 'api/3/account', {})
.then(account => console.log(account));Get Transaction List:
transactions = make_api_request('POST', 'api/3/transaction/list', {
'transactionClass': 'RFSTRADE',
'accountGroupUuid': 'afe6280e-163a-4652-a795-34e963063b06'
})makeApiRequest('POST', 'api/3/transaction/list', {
transactionClass: 'RFSTRADE',
accountGroupUuid: 'afe6280e-163a-4652-a795-34e963063b06'
}).then(transactions => console.log(transactions));Postman examples
Setup Environment Variables
Create these variables in your Postman environment:
| Variable | Description | Example |
|---|---|---|
Rest-Key | Your API key | abc123... |
Rest-Secret | Your API secret (base64) | ZGVmNDU2... |
account_group_uuid | Your account group ID | afe6280e-163a-4652... |
Pre-Request Script
Add this to your request's "Pre-request Script" tab:
const moment = require('moment');
const CryptoJS = require('crypto-js');
// Update the path to match your endpoint
const path = 'api/3/transaction/list';
// Request payload - customize as needed
const requestBodyObj = {
transactionClass: 'RFSTRADE',
accountGroupUuid: pm.variables.get('account_group_uuid')
};
// Add tonce
requestBodyObj.tonce = moment().valueOf() * 1000;
const requestBodyString = JSON.stringify(requestBodyObj);
// Generate signature
const secret = CryptoJS.enc.Base64.parse(pm.variables.get('Rest-Secret'));
const hmac = CryptoJS.algo.HMAC.create(CryptoJS.algo.SHA512, secret);
hmac.update(path + '\0' + requestBodyString);
// Set variables for request
pm.variables.set('Rest-Sign', CryptoJS.enc.Base64.stringify(hmac.finalize()));
pm.variables.set('postBody', requestBodyString);Frequently Asked Questions
Q: Why do I need to include a tonce in every request?
A: The tonce (time-once) prevents replay attacks. Each tonce must be larger than the previous one, ensuring requests can't be intercepted and reused.
Q: What happens if my system clock is wrong?
A: If your tonce is more than 1 minute in the past or future, the request will be rejected. Ensure your system time is synchronized with NTP servers.
Q: Can I reuse a signature for multiple requests?
A: No. Each signature is valid for only one request. You must generate a new signature (with a new tonce) for every API call.
Q: Why is the null byte (\0) separator required?
A: The null byte separates the path from the body in the message, preventing certain types of signature manipulation attacks.
Q: How long is an API signature valid?
A: Each signature is single-use and expires when the tonce becomes older than 5 minutes or when a newer tonce is used.