Authentication

To connect, you must use a combination of the API Key and API Secret. These sets of credentials are specific to an account and cannot be generated by anyone except Zerocap. Zerocap will send you your credentials.

All REST requests must contain the following headers:

HeaderDescription
api-keyAPI key as a string.
signatureLowercase hex-encoded HMAC-SHA256 of the timestamp header value, using your API Secret as the key: hex(HMAC_SHA256(api_secret, timestamp)). Only the timestamp is signed; the request path and body are not.
timestampTimestamp for your request: Unix time in whole seconds, sent as a string, for example 1792064965.

All request bodies should have content type application/json and be valid JSON.

The timestamp header MUST be the number of seconds since Unix Epoch in UTC, as an integer. Decimal values are rejected with HTTP 401, and a timestamp in milliseconds is rejected as expired. Your timestamp must be within 30 seconds of the API service time or your request is considered expired and will be rejected with HTTP 408.

Get Time (GET /v2/systems/get_time) is the only endpoint that needs none of these headers. It returns the API server's Unix time in seconds as a string, for example {"server_time": "1792064965"}. It is the only way to check your clock against the API's clock, so call it before signing if your clock may drift.

If your API key was issued with an IP allow-list, REST requests from any other IP address are rejected with HTTP 401 Unauthorized IP address.

To connect to Websocket, the initial connection must contain the same headers as a REST request. After the connection is authenticated there is no further need to send the headers. See Websocket Overview.

Environments

EnvironmentREST base URLWebsocket URL
Productionhttps://dma-api.zerocap.com/v2wss://dma-ws.zerocap.com/v2
Sandboxhttps://sandbox-api.zerocap.com/v2wss://sandbox-ws.zerocap.com/v2

Append the endpoint path to the base URL with a single /, for example https://sandbox-api.zerocap.com/v2/orders/get_instruments. A double slash (//v2/...) is answered with HTTP 401. In the Python SDK, pass env='prod' or env='sandbox'.

Example

import time
import hmac
import hashlib
import requests

api_key = ""
secret_key = ""
zerocap_url = "https://sandbox-api.zerocap.com/v2"  # production: https://dma-api.zerocap.com/v2

def hashing(secret_key, timestamp):
    return hmac.new(secret_key.encode("utf-8"), str(timestamp).encode("utf-8"), hashlib.sha256).hexdigest()

def get_headers():
    timestamp = int(time.time())  # whole seconds
    signature = hashing(secret_key, timestamp)
    headers = {
        'api-key': api_key,
        'signature': signature,
        'timestamp': str(timestamp)
    }
    return headers

def get_time():
    # Public endpoint: no authentication headers
    response = requests.get(zerocap_url + '/systems/get_time', timeout=30)
    return int(response.json()['server_time'])

def get_instruments():
    headers = get_headers()

    response = requests.get(zerocap_url + '/orders/get_instruments', headers=headers, timeout=30)
    result = response.json()
    return result

def run():
    print("clock offset (seconds):", get_time() - int(time.time()))
    print(get_instruments())

if __name__ == '__main__':
    run()

Errors

Authentication is checked before the request reaches the endpoint. These failures return a body with a single message:

HTTP statusBodyCause
401{"message": "Unauthorized."}A header is missing, or the API key is unknown.
401{"message": "encryption check is fail"}The signature does not match the timestamp and your API Secret.
401{"message": "invalid timestamp"}The timestamp is not a whole number of seconds.
401{"message": "Unauthorized IP address."}Your key has an IP allow-list and the request came from another address.
408{"message": "request timeout"}The timestamp is more than 30 seconds away from the server time.
400{"message": "Invalid JSON format in request body"}A POST body that is not valid JSON.

Every other error has this shape:

{
    "error": {
        "code": 500,
        "description": "Too Many Requests"
    }
}
HTTP statusCause
200The trading engine refused the request, for example a buy order priced below the market. The body holds only error: code is the engine's error code (10502 in that example) and description the reason. Check every 200 response for an error key.
400The request was refused. The reason is in error.description.
404Unknown path: {"error": {"code": 404, "description": "Endpoint not found. "}}
422A parameter failed validation. This body uses a different shape, {"detail": [{"loc": [...], "msg": "...", "type": "..."}]}, with one entry per invalid field.
429Rate limit exceeded, see below.
500Unexpected server error: {"error": {"code": 500, "description": "System Error"}}

error.code is an application code, not the HTTP status: 400 and 429 responses carry "code": 500.

Rate limits

Each endpoint accepts up to 50 requests per second from each client IP address, counted separately for each endpoint. Requests over the limit are answered with HTTP 429 and {"error": {"code": 500, "description": "Too Many Requests"}}. Wait one second before retrying.


Did this page help you?