Skip to main content

Endpoint Security Type

ZTDX accepts two authentication mechanisms. Each endpoint declares which one it requires:

MechanismHeader / BodyUsed by
HMAC SHA-256 signatureX-MBX-APIKEY: <api_key> + signature/fapi/v1/* programmatic / market-making clients
EIP-712 wallet signatureAuthorization: Bearer <JWT> (after login)/api/v1/* interactive / browser clients

Public endpoints (market data, exchange info, public leaderboards) require no signature.

HMAC SHA-256 Signature

This is the Binance-compatible signing flow. Use it for any client that trades programmatically.

Steps

  1. Create an API Key. See Quick Start.
  2. Build a string to sign — for GET / DELETE, the URL query string (e.g. symbol=BTCUSDT&side=BUY&timestamp=…); for POST / PUT, the query string concatenated with the request body.
  3. Compute HMAC_SHA256(secret_key, payload) and append it to the URL as &signature=<hex>.
  4. Send the request with X-MBX-APIKEY: <api_key>.

Required parameters

NameDescription
timestampUnix milliseconds, must lie within [serverTime − 60000ms, serverTime + 60000ms] (fixed ±60s window, enforced in both directions)
recvWindowAccepted for Binance compatibility but ignored — the window is fixed at ±60000ms and cannot be tightened or loosened.
signatureThe hex-encoded HMAC SHA-256

A request whose timestamp falls outside the window returns HTTP 401 with the standard error envelope:

{
"success": false,
"data": null,
"error": {
"code": "SIGNATURE_INVALID",
"message": "Timestamp outside recv window"
},
"timestamp": 1778716800
}

Payload encoding tolerance

Some HTTP libraries URL-encode special characters in the query string (e.g. [%5B, ,%2C) before hashing; others sign the raw form. ZTDX accepts both: the server tries the raw payload first and falls back to a URL-decoded comparison if that fails. Clients do not need to align with a specific encoding before signing.

Example (Python)

import time, hmac, hashlib, requests

API_KEY = "your_api_key"
API_SECRET = "your_api_secret"
BASE_URL = "https://api.prex.world"

def sign(payload: str) -> str:
return hmac.new(API_SECRET.encode(), payload.encode(), hashlib.sha256).hexdigest()

def signed_get(path: str, params: dict):
params["timestamp"] = int(time.time() * 1000)
qs = "&".join(f"{k}={v}" for k, v in params.items())
return requests.get(
f"{BASE_URL}{path}?{qs}&signature={sign(qs)}",
headers={"X-MBX-APIKEY": API_KEY},
)

print(signed_get("/fapi/v1/openOrders", {"symbol": "BTCUSDT"}).json())

EIP-712 Wallet Signature

Used by the front-end and any client that authenticates with the user's wallet.

Login flow (nonce → typed-data sign → JWT)

  1. Fetch a nonce: GET /api/v1/auth/nonce/:address returns the user's current nonce together with a ready-to-sign EIP-712 typed_data object (type Login(address wallet,uint256 nonce,uint256 timestamp)). A user row is created automatically on first call.
  2. Sign the typed data with the wallet (e.g. eth_signTypedData_v4).
  3. Exchange for a JWT: POST /api/v1/auth/login with { "address", "signature", "timestamp" }. The timestamp (Unix seconds) must be within ±300 s of server time, otherwise the request fails with 400 TIMESTAMP_EXPIRED. On success the nonce is incremented (each signature is single-use) and the response is { "token", "expires_at" }.
  4. The JWT is valid for 86400 seconds (24 h) by default.

Once issued, the JWT is sent as Authorization: Bearer <jwt>. Most trading actions on /api/v1/* additionally require an EIP-712 signature inside the request body that covers the action's structured payload — each endpoint's page lists its TypeHash.

Which mechanism works on which routes

Both authentication mechanisms are handled by the same middleware, which is mounted on the protected routes of both surfaces (/api/v1/* and /fapi/*). In practice:

Credential/api/v1/*/fapi/v1/*
API Key + HMAC (X-MBX-APIKEY)Works — and the per-request EIP-712 body signature is waived for API-key callersWorks (the documented/primary use)
JWT (Authorization: Bearer)Works (the documented/primary use); body EIP-712 signature required on trading actionsWorks

The middleware checks X-MBX-APIKEY first; if the header is absent it falls back to Bearer-JWT validation. The table on top of this page describes the intended pairing, not a hard restriction.