Kronos X Member API
Kronos X is a multilateral trading facility for perpetual futures and spot crypto-asset markets. This documentation covers everything a member firm needs to connect: three transports over one deterministic matching engine, with identical semantics, identical risk controls and one shared recovery model.
Member portal
Alongside the API, every member firm gets a web portal. Venue operations issue the login (your member code and a password) during onboarding; since 19 August 2026 the sign-in also asks for a six-digit code from an authenticator app, enrolled at your first sign-in from a QR code on the sign-in page (Member Portal User Guide, section 1.1). API keys and the API itself are unaffected.
- Positions and risk
- Balances, equity, initial and maintenance margin, your equity-to-MM ratio and risk status, member-net positions with unrealised profit and loss, and the ADL queue rank per position.
- Markets and your orders
- Live order book with depth, recent venue prints, your working orders, and your untriggered conditional orders shown alongside the visible book.
- RFQ trading
- The full request-for-quote surface for RFQ-mode markets: request quotes with a ticket (including barrier terms on knock-out markets), watch incoming quotes and accept, quote other members' requests from the open board, withdraw your standing quotes, and track your live barrier contracts with entry, barrier, take profit, distance to knock-out, unrealised profit and loss and margin, plus your recently settled contracts with their termination reason. Once venue operations have registered Authorised Users for your member, the tab's Trader ID field carries the Authorised User's id on every request, quote and acceptance (code 2021 without it; cancels are never gated).
- End clients
-
Per-
clientUidsub-positions and activity. Margin and liquidation still run on your member net position; these records are the exact attribution behind it. - Reports
- Trade and ledger history with CSV download, and activity statistics per instrument and per end client, generated from the venue's durable records.
- Credentials and limits
- Your live rate limits and order-to-trade regime, your registered Authorised Users, a kill switch, and the keys card: self-service issuance and revocation of your API credentials. A member holds up to 5 keys per scope (TRADE and READ_ONLY); issuing a key adds it and existing keys keep working, so rotation is a failover: issue the new key, migrate traffic, revoke the old one. A new key takes effect immediately and its secret is shown once; revocation is per key, immediate, and ends live WebSocket and FIX sessions using that key. You can also change your portal password there, under Credentials.
What makes this venue different
Four properties shape every integration decision. They are worth reading before you write code, because each one removes a class of bug that is expensive to find later.
- Deterministic core
- A single-writer matching engine with exact integer arithmetic. The same command sequence always produces the same state, byte for byte, so any dispute about a figure is answerable by reproducing it from the verified snapshot with the binary version that produced it, rather than by argument.
- Ack after durable
- No acknowledgement and no event leaves the venue before the journal has made it durable. When you hold an ack, the venue cannot forget the order, even through power loss.
- One recovery model
- Every transport shares a single durable, totally ordered event stream. After any disconnect you page it from a cursor and prove you missed nothing. There is no per-transport replay to implement.
- Member-level risk, client-level records
- Margin, position limits and liquidation are computed on your member net position. Per-end-client sub-positions are kept exactly, for reporting and reconciliation, and are never read by the risk engine.
Environments
| Environment | REST | WebSocket | FIX 4.4 |
|---|---|---|---|
| Sandbox | https://sandbox.mtf.perpetuals.net |
wss://sandbox.mtf.perpetuals.net |
sandbox.mtf.perpetuals.net:9879 |
| Production | https://production.mtf.perpetuals.com |
wss://production.mtf.perpetuals.com |
confirmed at onboarding |
All transports are TLS only with a publicly trusted certificate. No certificate
pinning is required. WebSocket paths are /ws/public for market data
and /ws/private for account data and order entry.
The sandbox host is sandbox.mtf.perpetuals.net since Member API
revision 3.83: three nodes behind that one entry host, replacing the
single-node sandbox.mtf.perpetuals.com from the date venue
operations announce. Each environment publishes one entry host, which sends
new connections to the node that accepts writes; never pin a node. A leader
change therefore happens on the sandbox as it can on production: handle the
5030 redirect, the WebSocket close 1001
leader change, reconnect and the FIX leader-change Logout as the
REST, WebSocket and
FIX pages describe.
Sandbox balances are test money and carry no value. The sandbox runs a price simulator so marks, funding and triggers behave realistically. Full sandbox data resets are announced at least 5 business days in advance via the members Slack channel and a portal notice, and the announcement names the post-reset baseline; API credentials survive a reset unless the announcement says otherwise, and portal logins persist. Margin top-ups and per-member state resets for test members are available on request via the members Slack channel. The full sandbox operations policy is section 1a of the Member API reference.
Build and certify on sandbox. Production endpoints are enabled per member at onboarding, once conformance testing is complete, and production credentials are issued separately from sandbox credentials. Point your integration at the sandbox host until venue operations confirm your production access.
Quickstart
Fetch reference data, sign one private request, place an order and cancel it.
Set KRONOSX_API_KEY and KRONOSX_API_SECRET in your
environment first.
import os, time, hmac, hashlib, json, requests
BASE = "https://sandbox.mtf.perpetuals.net"
KEY = os.environ["KRONOSX_API_KEY"]
SECRET = os.environ["KRONOSX_API_SECRET"]
def signed(method, path, body=None):
raw = json.dumps(body, separators=(",", ":")) if body is not None else ""
ts = str(int(time.time() * 1000))
sig = hmac.new(SECRET.encode(), (ts + method + path + raw).encode(),
hashlib.sha256).hexdigest()
headers = {"X-API-KEY": KEY, "X-TIMESTAMP": ts, "X-SIGNATURE": sig,
"Content-Type": "application/json"}
# Send the SAME bytes that were signed.
return requests.request(method, BASE + path, headers=headers,
data=raw or None, timeout=10).json()
# 1. Reference data drives all rounding. Never hard-code ticks or quantity steps.
instruments = requests.get(BASE + "/v1/instruments", timeout=10).json()["data"]
btc = next(i for i in instruments if i["symbol"] == "BTC-USD-PERP")
print(btc["symbol"], "tick", btc["tickSize"], "step", btc["qtyStep"], "minNotional", btc["minNotional"])
# 2. Account state. Note: data is an ARRAY, one row per settlement currency.
account = signed("GET", "/v1/account")["data"][0]
print("balance", account["balance"], account["ccy"])
# 3. Place a resting order. clOrdId is yours and is the idempotency key.
order = signed("POST", "/v1/orders", {
"symbol": "BTC-USD-PERP", "side": "BUY", "type": "LIMIT", "tif": "GTC",
"price": "60000", "qty": "0.005",
"clOrdId": "quickstart-1", "clientUid": "CLIENT-A",
})
print(order["code"], order["data"]["status"], order["data"]["orderId"])
# 4. Cancel it.
print(signed("DELETE", "/v1/orders/" + str(order["data"]["orderId"]))["data"]["status"])
import crypto from "node:crypto";
const BASE = "https://sandbox.mtf.perpetuals.net";
const KEY = process.env.KRONOSX_API_KEY;
const SECRET = process.env.KRONOSX_API_SECRET;
async function signed(method, path, body) {
const raw = body === undefined ? "" : JSON.stringify(body);
const ts = Date.now().toString();
const sig = crypto.createHmac("sha256", SECRET)
.update(ts + method + path + raw).digest("hex");
const res = await fetch(BASE + path, {
method,
headers: { "X-API-KEY": KEY, "X-TIMESTAMP": ts, "X-SIGNATURE": sig,
"Content-Type": "application/json" },
body: raw || undefined, // the exact bytes that were signed
});
return res.json();
}
const { data: instruments } = await (await fetch(BASE + "/v1/instruments")).json();
const btc = instruments.find(i => i.symbol === "BTC-USD-PERP");
console.log(btc.symbol, "tick", btc.tickSize, "step", btc.qtyStep, "minNotional", btc.minNotional);
const account = (await signed("GET", "/v1/account")).data[0];
console.log("balance", account.balance, account.ccy);
const order = await signed("POST", "/v1/orders", {
symbol: "BTC-USD-PERP", side: "BUY", type: "LIMIT", tif: "GTC",
price: "60000", qty: "0.005", clOrdId: "quickstart-1", clientUid: "CLIENT-A",
});
console.log(order.code, order.data.status, order.data.orderId);
const cancel = await signed("DELETE", `/v1/orders/${order.data.orderId}`);
console.log(cancel.data.status);
# Public endpoints need no signature.
curl -s https://sandbox.mtf.perpetuals.net/v1/instruments | jq '.data[].symbol'
# Signed GET /v1/account
BASE=https://sandbox.mtf.perpetuals.net
TS=$(($(date +%s) * 1000))
SIG=$(printf '%s' "${TS}GET/v1/account" \
| openssl dgst -sha256 -hmac "$KRONOSX_API_SECRET" -hex \
| sed 's/^.*= //')
curl -s "$BASE/v1/account" \
-H "X-API-KEY: $KRONOSX_API_KEY" \
-H "X-TIMESTAMP: $TS" \
-H "X-SIGNATURE: $SIG" | jq .
Authentication
Every private request carries three headers:
| Header | Value |
|---|---|
X-API-KEY | Your API key. |
X-TIMESTAMP | Unix milliseconds, as a string. |
X-SIGNATURE |
hex_lowercase(HMAC_SHA256(secret, timestamp + METHOD + pathWithQuery + body)) |
X-RECV-WINDOW |
Optional, milliseconds, default 5000, an integer from 1 to 60000 (any other value is refused with code 1002, revision 3.88). The timestamp may lag the venue clock by up to the window and lead it by at most 5000 ms; outside that the request is rejected with code 1002. |
A signed write is accepted once (revision 3.88): the same
X-API-KEY and X-SIGNATURE on any method but GET
answers 1002 request already received and nothing runs, so sign
every send, a retry included, with a fresh timestamp and keep its
clOrdId. The private WebSocket login signs the connection's
challenge nonce, the first frame of every /ws/private connection
(WebSocket page).
METHODis uppercase.pathWithQueryis the path plus query string exactly as sent, with no host, for example/v1/orders?symbol=BTC-USD-PERP.bodyis the exact raw JSON string you transmit, or an empty string when there is no body.
aiohttp/yarl re-encodes commas), so sign the exact
final string your client transmits. A comma-bearing test vector is on the
REST page.
Verify your signer against this vector before connecting:
secret = "test-secret"
timestamp = "1700000000000"
method = "GET"
path = "/v1/account"
body = ""
payload = "1700000000000GET/v1/account"
signature = 741b61b6d3740ede7864551612095d2fb73b75a4f70534f45e9e207bdd1f321c
Credential scopes
Credentials carry a scope. A TRADE credential can do everything. A READ_ONLY credential, also called a drop copy, may read and receive the full event stream but is refused on every write, on every transport, with code 1005: order entry, cancellation, amendment and configuration changes are all impossible with it.
Issue a READ_ONLY credential for back office, risk, surveillance and reporting systems, so those components provably cannot trade. A member may hold up to 5 keys of each scope at the same time: issuing a key adds it and never revokes another, and revocation is explicit, per key, immediate, and ends live WebSocket and FIX sessions using the revoked key while the member's other keys keep working. Secrets are shown exactly once, at issuance.
Conventions
Response envelope
{ "code": 0, "msg": "ok", "data": { }, "asOfSeq": 123456789, "ts": 1700000000000 }
asOfSeq is the engine sequence number the response reflects. It is
a total order across the entire venue and is directly comparable with WebSocket
frames, FIX ExecID values and the recovery cursor, which is what
makes cross-transport reconciliation mechanical rather than heuristic.
Rules that prevent real bugs
-
Exact decimal strings. Every price, quantity, rate and money amount is
a JSON string, for example
"65000.5". Parse into a decimal type. Binary floating point will eventually produce a reconciliation break. - Strict request bodies. Order entry rejects unknown JSON fields with code 2017 rather than ignoring them. If you believed a field did something, the venue tells you it does not, instead of silently working the order without it.
- Tolerant response parsing. The venue adds response fields, event types and enum values without notice. Ignore what you do not recognise; never fail on an unknown value.
-
Two-level success check.
code: 0means the gateway accepted the request. An order can still be rejected by the engine inside a 200 response: checkdata.status == "REJECTED"and readdata.rejectCode. -
Reference data drives rounding. Read
GET /v1/instrumentsat startup and align every price totickSizeand every quantity toqtyStep. Quantities are decimal base-asset units, andprice x qtymust reach the instrument'sminNotional(code2028below it). Never hard-code instrument parameters.
Identifiers
| Field | Meaning |
|---|---|
clOrdId |
Your order reference, up to 36 characters, unique per member per 24 hours.
It is also the idempotency key: resending an identical payload returns the
original order with duplicate: true, while reusing the
reference with a different payload is rejected with 2009. The
KX- prefix is reserved for venue-generated references.
|
clientUid |
Your end-client reporting identifier, required on every order. Opaque to the venue, which holds no name, KYC or personal data. It rides every response and notification, including rejections and venue-initiated closes, so all activity reconciles to one end client. |
orderId |
The venue's identifier for an accepted order. |
Rate limits
Two independent token buckets per member, shared across all of the member's
API keys: 6000 reads per minute and a
40000 per minute order budget. Order placements, batch entries, and
amendments all consume the order budget, one unit each; cancels are never
throttled. Order-budget traffic does not consume a read token, so heavy order
flow never exhausts your read allowance and heavy polling never throttles your
order flow. Breach returns code 1003. A batch
(POST /v1/orders/batch, WS orders.batch) is charged
one unit per entry up front, all or nothing: a denied batch answers 1003 and
consumes nothing.
Both buckets are full-capacity and refill continuously, so after a quiet period the whole minute's allowance is available at once. The order bucket is sized deliberately for a member that must push a burst of risk-reducing orders when many end clients liquidate in a short window. Cancels consume neither bucket.
Because the buckets refill continuously rather than at a window edge, a
1003 tells you exactly how long to wait: data.retryAfterMs
on REST and WebSocket, the Retry-After and
X-RateLimit-Retry-After-Ms headers on REST, and
Text(58) on FIX. At the default order capacity that wait is
typically one or two milliseconds. Sleep it and resend, rather than pausing for
a window that does not exist.
Separately, the order-to-trade regime under RTS 9 counts order messages against trades over a sliding 60 minute window; every batch entry counts as one order message. A block returns code 1004 on new orders, batches and amendments only; cancels are never blocked, so you can always withdraw liquidity.
Choosing a transport
| Need | Use | Why |
|---|---|---|
| Requests, history, reconciliation, recovery | REST | The only transport carrying dated history and the cursor backfill. |
| Live market data and account events | WebSocket | The widest feed: incremental checksummed books, mark and index prices, funding, typed venue events and guaranteed private channels. FIX 4.4 streams a book and the public trade tape too, so compare the two before adding a socket you may not need. |
| High-rate order entry | WebSocket | Order operations can be pipelined, which removes the round-trip ceiling that limits a serialized REST client. |
| RFQ trading, barrier contracts and yield contracts | REST + WebSocket | The negotiation flow is REST (create, board, quote, accept; on swap markets offer, board, take) with lifecycle pushes on the private rfq, barriers and swaps channels. RFQ markets have no FIX surface. |
| Existing FIX infrastructure | FIX 4.4 | Standard order entry, a full-member execution report drop copy, and streaming market data: snapshot 35=W, incremental 35=X and the public trade tape. |
| Back office and risk observation | Any, with a READ_ONLY credential | Provably cannot trade, enforced at the credential. |
Most members run REST and WebSocket together. FIX members still need REST, because recovery after a disconnect is a REST call by design.
Market data: FIX or WebSocket. Both carry a real streaming book. FIX
gives you a snapshot (35=W) then incremental New, Change and
Delete levels (35=X) plus a print-by-print trade tape on the same
session as your orders, which is enough for most order-entry and broker
integrations. The WebSocket carries strictly more: per-level book checksums
for continuous verification, mark and index prices, funding, the typed
venue-events feed and the sequence-cursored recovery backfill. Run FIX alone
if you need order entry with a book and a tape; add a WebSocket connection if
you need any of the rest.
Onboarding checklist
- Receive sandbox credentials from venue operations.
- Verify your signer against the published vector above.
- Fetch
/v1/instrumentsand drive all rounding from it. - Exercise every order type you intend to use, including the rejection paths.
- Maintain an order book from the WebSocket with checksum verification for at least five minutes without a mismatch.
- Kill the connection mid-flow and prove gap-free recovery from the event cursor.
- Handle the venue-initiated flow: executions and cancels for orders you never sent.
- Reconcile per-end-client sub-positions against the end-of-day snapshot.
- Issue a READ_ONLY credential for your back-office systems.
- Agree rate limits and order-to-trade thresholds with venue operations.
Support
Venue operations handle credentials, limits, instrument onboarding and incidents. Sandbox resets and production releases are announced in advance (a release that stops production at least five Business Days ahead on the Website, inside the standard maintenance window of Saturday 02:00 to 06:00 UTC, Member API section 10.7; full sandbox data resets at least 5 business days ahead, via the members Slack channel and a portal notice; the policy is section 1a of the Member API reference). Contract changes follow the versioning policy: additive changes ship without notice and your parsers must tolerate them, while breaking changes ship only under a new major version path or with at least 30 days written notice and a dated migration table.