Codes and enumerations
Every numeric code and every enumerated string value Kronos X can put on the wire, in one place. The numeric registry is a single venue-wide namespace shared by REST, WebSocket and FIX: a code number is never reused and never repurposed, and new codes are only ever added.
msg text. Messages are advisory, vary by call site for the same code, and are reworded without notice. Codes never change meaning. Treat an unknown code number or an unknown enum string as "new, and not a bug in my parser": log it, take the conservative path, do not crash the session.Where codes appear
Six distinct surfaces carry codes. Four of them share the numeric registry below. Two do not: WebSocket close codes and the FIX market-data reject reason are separate small sets, both spelled out here. The last row of the table is not a code surface at all: it is listed so the whole closed vocabulary sits in one place.
| Surface | Fields | Notes |
|---|---|---|
| REST response envelope | code (int), msg (string) | code: 0 means the gateway accepted the request. Nonzero means the gateway rejected it, and the HTTP status matches the mapping table below. On order-entry rejections data.clOrdId is echoed whenever the body parsed far enough to read it. |
| Engine order rejection | data.status: "REJECTED" plus data.rejectCode (int) | Arrives INSIDE a 200 with code: 0. The gateway accepted the request; the engine rejected the order. Same numeric registry. |
| WebSocket op acks | success (bool), code (int), msg | Same registry. code: 0 and success: true on acceptance. The ack always echoes op and reqId, including on failures, so a pending request always resolves. |
| FIX 4.4 | tag 9010 | Carries the venue code on rejects. OrdRejReason(103) is always 99 (other): the precise reason is in 9010, never in 103. |
FIX MarketDataRequestReject(35=Y) | MDReqRejReason(281) | Separate set, not registry codes and never accompanied by a 9010: 0 unknown symbol or unsupported request shape, 2 the session is at its 32 subscription limit, the engine was momentarily busy, or the snapshot could not be built (all three retryable), 4 unsupported SubscriptionRequestType(263), 6 unsupported MDUpdateType(265), 8 unsupported MDEntryType(269). A rejected request subscribes nothing; Text(58) carries the plain-language reason. Full contract on the FIX page. |
| WebSocket close frame | close status code | Separate set: 1000, 1001, 4008, 4009. Not registry codes. |
| Payload enumerations | various string fields | Closed sets, listed later on this page, with the documented exceptions treated as open. |
REST envelope rejection
A gateway-level rejection never reaches the engine. Nothing was journaled, nothing happened, and the order does not exist.
{
"code": 2003,
"msg": "price outside band",
"data": { "clOrdId": "mm-7741" },
"asOfSeq": 0,
"ts": 1785000400123
}
Engine rejection inside a 200
This is the single most common integration bug on the venue. The HTTP status is 200 and the envelope code is 0, because the gateway did its job: it authenticated you, metered you, parsed the body and handed a valid command to the engine. The engine then refused the order on matching or risk grounds. A client that checks only the HTTP status or only the envelope code will book this order as live and then wait forever for a fill.
{
"code": 0,
"msg": "ok",
"data": {
"orderId": 0,
"clOrdId": "mm-7742",
"symbol": "BTC-USD-PERP",
"status": "REJECTED",
"rejectCode": 2005,
"qty": "1.5",
"remainingQty": "0"
},
"asOfSeq": 904118,
"ts": 1785000400456
}
code == 0 AND data.status != "REJECTED". The same rejection also arrives asynchronously on the private order WebSocket channel as a frame with type: "OrderRejected" carrying the same rejectCode, and on FIX as an ExecutionReport with 150=8, 39=8 and the code in tag 9010. The durable backfill row for that frame is stored under the event type ORDER_REJECTED, so the two spellings describe the same event on two surfaces.WebSocket op ack
{
"op": "order.place",
"reqId": "c-119",
"success": false,
"code": 1005,
"msg": "this credential is read-only (drop copy)"
}
Null members are omitted, not sent as null: a failure ack carries no data key at all. The msg text differs from the REST wording for the same code, which is exactly why you branch on the number.
Read ops that succeed also carry asOfSeq, the engine sequence number the answer is true as of. Use it to order snapshots against the live event stream.
FIX tag 9010
An order rejected by the engine comes back as an ExecutionReport with OrderID(37)=0. ExecID(17) is {asOfSeq}-{eventIndex} on an engine reject; a reject raised by the gateway before the engine is reached carries a gateway-generated ExecID instead. Session-level tags are omitted here for brevity:
8=FIX.4.4|35=8|37=0|11=mm-7743|1=cli-4471|17=904118-2|
150=8|39=8|55=BTC-USD-PERP|54=1|151=0|14=0|103=99|9010=2003|
58=price outside band|60=20260801-10:14:22.517|
35=9 and carries CxlRejReason(102) plus free text in tag 58, not a registry code. 102=1 means the order is unknown to the venue (registry equivalent 2014); 102=0 means the cancel or replace failed for another reason, including a rate-limit or OTR block. If you need the numeric registry code for a cancel failure, cancel over REST or WebSocket instead.Other FIX tags
| Tag | Direction | Meaning |
|---|---|---|
9001 | inbound | Y = reduce-only. |
9002 | inbound | Trigger source: LAST (default), MARK, INDEX. |
9003 | both, on Logon | Drop-copy session. A READ_ONLY credential logs on as drop copy regardless of what you send, and the venue echoes 9003=Y. |
9004 | both, on Logon | Y arms cancel-on-disconnect for the session (trading sessions only). The Logon reply echoes the state actually armed, so a drop-copy session that sent Y reads N back. See the FIX page. |
9005 | inbound, on Logon | Cancel-on-disconnect grace window in milliseconds, clamped 0 to 30000; absent means 0. |
9010 | outbound | The venue's numeric registry code on an ExecutionReport reject. |
9011 | outbound | MEMBER or VENUE on fill ExecutionReports. This is refSource: key your unsolicited-close handling on it. |
9012 | inbound, on NewOrderSingle | The Trader ID of the Authorised User entering the order, the FIX spelling of reg.traderId. Required once the member has registered Authorised Users, otherwise 2021. |
9015 | outbound | Y on the ExecType(150)=I restatement that answers a reused ClOrdID with an identical payload, the FIX spelling of duplicate: true. Absent on every other report. See Reusing a ClOrdID. |
9016 | both, on NewOrderSingle and its ExecutionReports | Hedge-mode position side, LONG or SHORT, the FIX spelling of positionSide: routes the order's fills to that sub-position bucket of the end client. Absent means netting, and it is echoed on every report for an order that carried it, omitted on netting orders. Order-book perpetual markets only (2016 on spot, 2013 on any other value). See the bucket model. |
9013 | outbound, on 35=X trade entries | Aggressor side of the print: 1 buy, 2 sell. The FIX spelling of the side field on the WebSocket trades channel. |
9014 | outbound, on 35=X trade entries | Trade classification: NORMAL, LIQUIDATION, ADL, RFQ, BARRIER_CLOSE, SWAP, REVERSAL, AUCTION. Single value, not a set, and the same value the WebSocket trades channel publishes as flags. |
9017 | outbound, on the accept report | Queue position at rest, quantity ahead (decimal base units, FIFO priority at the order's price level). Only on the 150=0 report of a placement that RESTED the order; 0 means front of the level, so presence is the signal that the order rested. A snapshot at rest time, immediately stale. |
9018 | outbound, on the accept report | Queue position at rest, orders ahead (integer). Beside 9017, same presence rule: both tags appear together or not at all. |
9021 | inbound, on NewOrderSingle | reg.tradingCapacity: DEAL, MTCH or AOTC. Tags 9021 to 9027 and 9029 carry the remaining REST/WS reg object fields, recorded verbatim into the same per-order regulatory record; an absent tag leaves the field unset and validation is shared across transports. Standard OrderCapacity(528) is also accepted for GATED members (P and G map to DEAL, R to MTCH, A to AOTC; at OFF the tag stays unparsed); 9021 stays canonical, at ENFORCE an unmappable or conflicting 528 refuses with 2033, at WARN it is counted and ignored. Under ENFORCE-stage capacity enforcement a violation answers 2033 before the engine. |
9022 | inbound, on NewOrderSingle | reg.clientIdType: LEI, NATIONAL_ID or INTERNAL. |
9023 | inbound, on NewOrderSingle | reg.clientId, the identifier that 9022 names. |
9024 | inbound, on NewOrderSingle | reg.investmentDecisionId. |
9025 | inbound, on NewOrderSingle | reg.executionDecisionId. |
9026 | inbound, on NewOrderSingle | reg.dea: Y means true, any other value reads as N, the 9001/9004 convention. |
9027 | inbound, on NewOrderSingle | reg.liquidityProvision: Y means true, same convention as 9026. |
9029 | inbound, on NewOrderSingle | reg.riskReducing, RTS 22 field 64 (FIX guide revision 2.20, Member API 3.70): Y means true, same convention as 9026; an absent tag leaves the field unset, absent = false on the report; only meaningful on commodity-derivative markets. NewOrderSingle only. |
HTTP status mapping
| HTTP | Used for |
|---|---|
| 200 | Accepted by the gateway. The engine may still have rejected the order inside the body. |
| 400 | Validation and risk rejections: most 2xxx and 3xxx codes, plus 5002 and 5003. |
| 401 | 1001, 1002 |
| 403 | 1005, 3002 |
| 404 | 2014, 2024, 2025, 2031, 2032 |
| 409 | 4001, and ERP bridge externalRef conflicts |
| 429 | 1003, 1004 |
| 500 | 5000, 5005 |
| 503 | 5001, 5004, 5006, 5007, 5030 |
The status is derived from the code, not the other way round. Do not infer a code from a status: 400 covers more than two dozen distinct codes.
Numeric registry
0, success
| Code | Name | Meaning |
|---|---|---|
| 0 | OK | Request accepted by the gateway. For order entry, still read data.status: a rejected order arrives with code: 0 and a rejectCode. |
1xxx, authentication and throughput
| Code | Name | HTTP | Returned when | What you do |
|---|---|---|---|---|
| 1001 | AUTH_FAILED | 401 | Missing or unknown API key, bad HMAC signature, missing X-API-KEY / X-TIMESTAMP / X-SIGNATURE, or a failed WebSocket auth op, a past expiresMs included. Also returned after a member is removed and its keys revoked. Since Member API revision 3.85 the WebSocket auth op also answers it when the frame carries no fourth arg (the pre-3.85 frame: auth failed: sign the nonce of this connection's challenge frame and send it as the fourth arg (Member API section 10.1)) or names a nonce other than its connection's challenge (auth failed: the nonce is not this connection's challenge). | Fix credentials or signing. The signature covers timestamp, method, path AND query string, and the raw body: a query rebuilt in a different parameter order will not verify. Do not retry unchanged. On the WebSocket read the connection's first frame, its challenge, and sign "GET/ws/private" + expiresMs + nonce, sending the nonce as the fourth arg; after a reconnect sign the new connection's nonce. |
| 1002 | TIMESTAMP_OUT_OF_WINDOW | 401 | X-TIMESTAMP lies more than the recv window behind the venue clock (X-RECV-WINDOW, default 5000 ms, at most 60000), or the timestamp is not an integer. Since Member API revision 3.85 also: a timestamp more than the smaller of the window and 5000 ms AHEAD of the venue clock; an X-RECV-WINDOW that is not an integer from 1 to 60000, refused rather than reduced; a signed write (any method but GET, POST /v1/margin/preview excepted) whose X-API-KEY and X-SIGNATURE the venue already accepted, kept for 60 seconds past its timestamp, so nothing of it runs (request already received); a signed write stamped before the venue node began to accept writes (its start or its promotion to leader); and a signed write stamped at or before the oldest write of the same member the node still records, once that one left the record (a stamp more than 60 seconds old, or your own writes filling the record far above every published rate). The window is checked when the headers arrive and again once the whole body has arrived. The WebSocket auth op never answers this code: a past expiresMs is 1001. | Sign the request again with a fresh timestamp and resend it, the same clOrdId on order entry and on RFQ requests and quotes; never resend identical bytes. Sync your clock over NTP: a clock more than 5 s ahead is refused whatever the window. Send X-RECV-WINDOW as an integer from 1 to 60000 or not at all. |
| 1003 | RATE_LIMITED | 429 | A per-member token bucket is empty, shared across the member's keys. Two INDEPENDENT buckets: reads (6000/min) and the order budget (40000/min). Order placements, amendments and every entry of a placement or amendment batch consume the order budget, one unit each; cancels, the entries of a cancellation batch included, are never throttled. A placement batch (POST /v1/orders/batch, WS orders.batch) and, since Member API revision 3.74, an amendment batch (POST /v1/orders/batch/amend, WS orders.batchAmend, charged after the venue's engine-busy check) are charged one unit per entry up front, all or nothing: a denied batch consumes nothing, and the units of a batch whose entries are refused after the charge stay spent. A cancellation batch (POST /v1/orders/batch/cancel, WS orders.batchCancel) is never charged. Order-budget traffic does not consume a read token, so heavy order flow never starves your read allowance and vice versa. Both are full-capacity buckets: after a quiet period the whole minute's allowance is available at once. Where your class publishes a per-second ceiling (STANDARD 1,000 orders/s on production, DMA 3,000, MM 5,000, the sandbox 0; section 4 of the Member API), a burst above it answers 1003 with retryAfterMs under one second even while the minute's allowance is unspent. Since Member API revision 3.70 the two public candle routes answer it from their own gate, and since Member API revision 3.73 a read of the history store answers it when more reads of yours of one route group are in flight on the node than it serves at once, or when the node's budget of these reads, which every member shares, is full: for these causes retryAfterMs is a SUGGESTED wait, not an exact refill, and a repeated 1003 there means your reads overlap, unless no other read of yours of the group was in flight, when it is the shared budget's. | Sleep data.retryAfterMs, then retry with the same clOrdId (a denied batch: the same batch). The buckets refill continuously, so that value is the exact wait; for a batch it covers the full N-unit deficit. REST also sets Retry-After and X-RateLimit-Retry-After-Ms; the WebSocket ack carries data.retryAfterMs; FIX puts it in Text(58). A higher allowance is only ever a published class of the rate-limit ladder whose objective criteria you meet (Member API section 4: DMA, MM, or another class the venue publishes); ask venue operations for the class assignment. A member-specific number is only ever tighter than your class; your class and the values in force are account.rateLimits on GET /v1/account/parameters. For a history read, serialise your readers. A 1003 of a history read while no other read of yours of the group is in flight comes from the node's shared budget, which serialising cannot help: sleep the wait and back off if it repeats. Since Member API revision 3.79 it also answers every request of yours that consumes the order budget once ten of your commands answered 5005 within 60 seconds, until the oldest of those ten is 60 seconds old: the refused request does nothing and consumes no order-budget unit, a request that consumes no order-budget unit (every cancellation among them) is never refused by it, and no other member is affected. A request that can only reduce your risk passes it too and still consumes its unit: a reduce-only order (reduceOnly true, FIX 9001=Y), a placement batch whose every entry is reduce-only, a lot close sent with reduceOnly true, and a barrier early close (the holder's close request, the writer's quote on it and the holder's accept of that quote; each passes only for your own contract, the read that tells them apart consumes one unit of your read budget, and while the venue sheds order entry, or when that read meets a full engine queue, it answers 5001 as below the budget while order-budget units remain); an amendment, and a placement batch with even one entry that is not reduce-only, are still refused. Such a refusal carries msg rate limit: repeated engine faults (5005) (FIX: Text(58) begins with it, before the wait), every other cause of this code keeps its text, and ten in 60 seconds is the published value, which production runs on every node. After 5005 answers (the fault budget of Member API section 4) the wait can be up to 60 seconds: sleep it, and never resend the commands that answered 5005 unchanged. |
| 1004 | OTR_BLOCKED | 429 | The RTS 9 order-to-trade regime block threshold is breached over a sliding 60 minute window. Blocks NEW orders, batches and amendments only, on the market whose ratio is breached. A placement batch with any entry on a blocked market is refused as a whole; since Member API revision 3.74 an amendment batch answers 1004 as the row of each entry naming an order on a blocked market and runs its other entries, and each of its entries counts as one order message when the venue hands it to the matching engine. Cancels always pass, a cancellation batch included, because a member must always be able to withdraw liquidity. Cancels are still COUNTED as order messages, as is every placement batch entry, so cancel churn and rejected batches raise your ratio. | Trade more or send less. The block clears as old minutes age out of the window; it is corrective, not a lockout. |
| 1005 | KEY_NOT_PERMITTED | 403 | The credential's scope is READ_ONLY (drop copy). Order entry, cancel, amend and configuration writes are refused on every transport, before the engine is reached. | Use your TRADE credential. Read-only keys exist so back-office, risk and surveillance systems provably cannot trade, which is why the refusal is at the credential and not at the route. |
countsAsRead: false: they never touch the read bucket. Order entry and amend take an order token and pass the OTR gate. A cancel takes NO token from either bucket and is never blocked by OTR, but it does increment the OTR message counter.2xxx, order and request validation
| Code | Name | HTTP | Returned when | What you do |
|---|---|---|---|---|
| 2001 | UNKNOWN_SYMBOL | 400 | The symbol does not exist on this venue, or the market was deleted. | Refresh GET /v1/instruments. Never hard-code the instrument list. |
| 2002 | TICK_LOT_MISALIGNED | 400 | Price is not on the tick grid, quantity is not an exact multiple of the instrument's qtyStep (the venue never rounds; quantities are decimal base-asset units and integer strings remain valid), a quantity or price that is zero, negative or out of range, or a price required by the order type that is missing or empty. A string that is not a decimal numeral at all (an exponent such as 1e-4, a blank, letters, a misplaced sign, a trailing point) is 2013 since Member API revision 3.60; before, it was 2002 too. Also the envelope reject of the three batch routes (POST /v1/orders/batch and, since Member API revision 3.74, /v1/orders/batch/amend and /v1/orders/batch/cancel, and their WebSocket ops) for a malformed body, a member beside the batch's one array or an entry count outside 1 to 50, and the row of a batch amendment entry whose price or quantity is off the order's grid. | Align to tickSize and qtyStep from /v1/instruments. Send decimal STRINGS, never floats: a float round-trip is the usual cause. |
| 2003 | PRICE_OUTSIDE_BAND | 400 | A price outside the instrument's band around the mark (priceBandBps): a limit order's price at placement or on amend, an RFQ quote's price, or, at RFQ ACCEPT time, a quote the mark has since drifted away from (staleness re-check against the current mark). Never a conditional order's (revision 3.83; kept by venue decision): neither the trigger price nor the limitPrice of a STOP_LIMIT or TAKE_PROFIT_LIMIT is checked against the band at placement, on amend or at activation, and its triggered limit child is not held to the market collar either, so it can rest outside the band and trade through the book up to its limitPrice. | Reprice nearer the market; on an RFQ accept, take a fresh quote. A conditional order never draws this code: bound its limitPrice yourself. |
| 2004 | UNSUPPORTED_ORDER_TYPE | 400 | An order type the venue does not run, including any trailing-stop request. | Use the supported set: LIMIT, MARKET, STOP_MARKET, STOP_LIMIT, TAKE_PROFIT_MARKET, TAKE_PROFIT_LIMIT. |
| 2005 | POST_ONLY_WOULD_CROSS | 400 (usually a rejectCode) | A POST_ONLY order would have traded on arrival. | Reprice passively and resubmit. Expected in normal quoting; do not treat as an error condition. |
| 2006 | FOK_UNFILLABLE | 400 (usually a rejectCode) | Historical registry entry. Current builds CANCEL an unfillable FOK with cancelReason: "FOK_UNFILLABLE" rather than rejecting it. Keep handling both shapes. | Treat as "nothing traded". |
| 2007 | NO_ELIGIBLE_LIQUIDITY | 400 | A MARKET order with no eligible opposing liquidity: an empty book, or nothing your Market Segments configuration permits you to trade with. | Wait for liquidity or send a LIMIT order. Check GET /v1/orderbook/matchable if you run a restricted segment. |
| 2008 | REDUCE_ONLY_VIOLATION | 400 | A reduceOnly order that could increase the member NET position: wrong side, no position, or the reducible budget is already committed by working orders. Member-scoped, not per end client. | Reduce size or drop the flag. Remember that reduce-only budget is netted at member level, so two desks can compete for the same budget. |
| 2009 | DUPLICATE_CLORDID | 400 | Two cases. ON AN ORDER: the clOrdId was used with a DIFFERENT payload while the venue still held the original reference. An identical payload does not error: it returns the original order with duplicate: true. The venue holds a reference for 24 hours, or until it has released it to stay inside its retention bound of the most recent 4,000,000 references venue-wide, whichever comes first; a reference whose order is still working or pending trigger is never released. Past that the reference is simply unknown, so a resend places a new order rather than rejecting. On FIX the rejection is 150=8 with 2009 in 9010, and the identical-payload path is a 150=I restatement of the original order carrying 9015=Y. ON AN RFQ REQUEST OR QUOTE, since Member API revision 3.85: ANY reuse of a clOrdId, identical or not, on an RFQ request (POST /v1/rfq: a request, a close request, a yield contract offer) or a quote (POST /v1/rfq/{rfqId}/quotes), one namespace with your orders and swap takes: nothing is opened or replaced, there is no restatement of the original, and msg reads clOrdId already used: an RFQ request or quote is never restated, read GET /v1/rfq/mine or GET /v1/rfq/quotes/mine (from the venue's replay-contract line for that release). | Generate unique references. Retries after a timeout must reuse the byte-identical payload, which is the deliberate idempotency mechanism. On an RFQ request or quote, read GET /v1/rfq/mine or GET /v1/rfq/quotes/mine for what your first send opened; a new request or a re-quote carries a new clOrdId. |
| 2010 | CLIENTUID_REQUIRED | 400 | An order without a clientUid from a member that is not market-maker house flow. | Send the end-client reporting identifier on every order. It is what makes your flow reportable and drives end-client STP scope. |
| 2011 | POSITION_LIMIT | 400 | The worst-case member net position after this order (net position plus same-direction working orders plus this order) would exceed the market's position cap. Where the market publishes maxPositionNotional, the worst case is valued at the CURRENT mark price and compared against that notional cap (decimal settlement-currency string; notional denomination gives every market uniform dollar capacity). Where no notional cap is set, the legacy base-unit maxPositionQty applies unchanged. Risk-reducing flow is never blocked, and a position pushed above the cap by mark drift alone is never forced to reduce: the cap gates increases only. | Reduce exposure or cancel same-direction working orders; the worst case counts them, not just the filled position. On a notional-capped market a mark move alone can take you to the cap, so size against the live mark. |
| 2012 | TOO_MANY_OPEN_ORDERS | 400 | The member is at its maximum open-order count. | Cancel stale orders. A member-wide cap above the 10,000 default is only ever a published class assignment (DMA or MM, the Member API section 4 ladder); a member-specific value is only ever tighter than your class. |
| 2013 | INVALID_ORDER | 400 | Field content the venue parsed but cannot accept: an unknown side or TIF value, a missing trigger on a conditional type, a trigger supplied on a non-conditional type, an unknown trigger direction or source, a missing trigger.limitPrice on STOP_LIMIT or TAKE_PROFIT_LIMIT, a body that parses to JSON null, or (since Member API revision 3.60) a qty or price string that is not a decimal numeral at all: an exponent such as 1e-4, a blank, letters, a misplaced sign, a trailing point (a numeral the grid cannot admit stays 2002), or (since Member API revision 3.72) a clOrdId, a clientUid or a Trader ID of the reg block (traderId, investmentDecisionId, executionDecisionId; on FIX ClOrdID(11), Account(1), the tags 9012, 9024 and 9025 and TargetOpenClOrdID(9028)) that begins or ends with whitespace, on order entry, per batch entry, on the RFQ routes and as the path identifier of the two identity routes, or (since Member API revision 3.74) the row of a batch amendment or cancellation entry that is not a JSON object, whose orderId is absent or below 1, that names both orderId and clOrdId or neither, whose clOrdId is empty, or that names an order an earlier entry of the same batch already names ("order N is already named by entry k of this batch": the first entry runs, the later one is not submitted), or (since Member API revision 3.84) a WebSocket order.cancelAll whose symbol or side is null or not a JSON string or is given twice, or whose args are given twice or are not exactly one object (none, null, an object, an empty array, two objects; msg names the member, for example symbol must be a string; nothing was cancelled; nothing is cancelled). ALSO the per-order size cap: where the market publishes maxOrderNotional, a single order, amend (full amended quantity), RFQ or quote whose price x qty exceeds it, valued at its effective price (priced orders at the limit price, market orders and RFQ creation at the current mark, quotes at the quoted entry price); reduceOnly is NOT exempt, venue-initiated closes are. Where no notional cap is set, the base-unit maxOrderQty applies unchanged. ALSO, on every market and in both arms, an absolute size ceiling: one order's price x qty must stay within the venue's exact-money range, 1000000000000000000 settlement-currency units. It is not published per market and sits orders of magnitude above every maxOrderNotional in force; no tradable order approaches it. On WebSocket it is also the "unknown op", "unknown channel" and malformed-frame ack. | Fix the request shape, or split the order so each message's value fits under maxOrderNotional (closing a large position may take several orders, by design; batch order entry sends up to 50 in one signed message). msg is diagnostic here, but still branch on 2013. |
| 2014 | ORDER_NOT_FOUND | 404 | A cancel or amend target does not exist, is not yours, or is already terminal. Since Member API revision 3.74 also the row of a batch amendment or cancellation entry naming such an order, or a clOrdId naming no open order of yours: the venue submits nothing for it (a cancellation entry whose order filled between the venue's lookup and its cancel answers it from the matching engine). Since Member API revision 3.86 also an amendment or a cancellation of a GTD order at or past its expireTs: the order is expired (status EXPIRED), the answer the request receives once the venue's expiry sweep has run; a cancel-all expires such an order and counts it in canceled. The matching engine applies this from its replay-contract line for revision 3.86, the first command of a venue started on the production contract; venue operations announce the date on any environment where that line lies ahead. | Treat as already closed. Reconcile via GET /v1/orders/{orderId} or the event stream before assuming anything else. |
| 2015 | SELF_TRADE_PREVENTED | 400 (usually a rejectCode) | Your STP policy is REJECT_TAKER and the incoming order would have matched your own order with the same clientUid. | Expected under that policy. Handle it like a reject, not like an outage. |
| 2016 | SPOT_UNSUPPORTED | 400 | reduceOnly, or a market-triggered conditional BUY, on a spot market. | Spot has no positions to reduce and prefunds the worst case, so neither can be honoured. Drop the flag or use a limit conditional. |
| 2017 | UNKNOWN_FIELD | 400 | STRICT order entry: the JSON body contains a field the venue does not define. Nothing is ever silently ignored. msg names the offending field. The same code covers every other deserialization failure on the order-entry and amend routes, including a wrong JSON type for a known field and a syntactically broken body; those cases carry msg: "malformed request body" and no field name. Every batch entry is parsed the same strict way (since Member API revision 3.74 the amendment and cancellation entries too), with case-sensitive member names, so Qty for qty is an undefined field there. Since Member API revision 3.84 also any request body on DELETE /v1/orders, which takes its filters as query parameters only (msg DELETE /v1/orders takes no request body: send clOrdId, symbol and side as query parameters; nothing was cancelled), and an args member of the WebSocket order.cancelAll other than symbol and side, its names case-sensitive (msg unknown field 'clientUid': order.cancelAll reads symbol and side only; nothing was cancelled); nothing is cancelled, where before the body or the member was ignored and the request ran on the rest: without clOrdId as a cancel of every open order the other filters matched, with it as the cancel of at most that one order. | Remove the field, or fix the type. If you believed the field did something, that belief was wrong and would have cost you money in production: re-read the order schema. This rule exists exactly so an unsupported parameter fails loudly instead of trading unhedged. |
| 2018 | ASSERTION_MISMATCH | 400 | The optional leverage or quoteCcy assertion does not match this instrument and YOUR account's effective margin. A leverage assertion holds only on a PERP with a single open-ended risk tier whose tier rate times your admin-set imMultiplier is exactly 1/leverage. | Read maxLeverage and leverageMode from /v1/instruments and imMultiplier from /v1/account. Treat 2018 as a configuration alarm on your side, not a retryable error. Neither field ever changes venue behaviour: they are assert-and-echo. |
| 2019 | INVALID_CURSOR | 400 | A malformed backfill cursor, or an unknown channel family on GET /v1/stream/events. A cursor is 30 ASCII digits: seq (19 digits, a signed 64-bit integer, 0 to 9223372036854775807), eventIndex (10 digits, a signed 32-bit integer, 0 to 2147483647: the 0-based position of an event among the events of one engine command, restarting at 0 for every seq) and leg (1 digit); member reference section 11.2. Since Member API revision 3.83 a well-formed 30-digit cursor whose seq or eventIndex field lies above its range answers this code with a msg naming the field and its range (afterCursor seq (characters 1 to 19) is out of range: it must be 0 to 9223372036854775807, afterCursor eventIndex (characters 20 to 29) is out of range: it must be 0 to 2147483647, the two joined by ; when both are); anything that is not 30 ASCII digits answers afterCursor must be 30 numeric characters, the text every malformed cursor answered before that revision. | Re-fetch a cursor from GET /v1/stream/cursor. Store the 30-character strings the venue returns and pass them back unchanged; do not do arithmetic on them, and branch on the code, never on the msg. |
| 2020 | RESERVED_CLORDID | 400 | clOrdId starts with the venue-reserved namespace KX-, case-insensitive. | Choose another prefix. KX- marks venue-generated references (KX-LQ-{orderId}, KX-ADL-{tradeId}), and rejecting member use of it is what keeps the two namespaces collision-free by construction. |
| 2021 | TRADER_ID_REQUIRED | 400 | The member has at least one registered Authorised User, and the order carries no reg.traderId, or one that is not a registered active Trader ID of that member. Enforcement is STAGED per member: a member with no registered Authorised Users is not gated at all, so an existing integration keeps working until its first user is registered. Order entry is enforced identically on REST, WebSocket and FIX, because all three transports build the order through the same path, and since revision 3.70 the member web portal's RFQ functions (request, quote and acceptance) apply the same check; cancels are never gated. Margin previews are exempt on both transports, POST /v1/margin/preview and the WebSocket get.marginPreview op alike: a preview is a read, not an order entry. | Send the venue-issued Trader ID of the Authorised User entering the order in reg.traderId, or in tag 9012 on FIX. Venue operations issue the id when they register the user, typically in the form TR-{memberId}-{8 hex characters}. Treat it as an opaque string of up to 64 characters. Do not retry unchanged. |
| 2022 | RFQ_MARKET_ONLY | 400 | An order sent to a market whose mode is RFQ. RFQ markets run no order book, so order entry is refused on every transport before any matching is attempted. | Trade the market through the RFQ flow starting at POST /v1/rfq. Read mode from GET /v1/instruments rather than inferring it from the symbol. The RFQ surface is REST and WebSocket only; it does not exist on FIX. |
| 2023 | NOT_RFQ_MARKET | 400 | An RFQ request targeting a market whose mode is ORDER_BOOK. The RFQ surface exists only on RFQ-mode markets. | Use the order book on that market. A market has exactly one mode, published on GET /v1/instruments: branch your routing on it, the same way you branch on kind. |
| 2024 | RFQ_NOT_FOUND | 404 | The RFQ does not exist, is not visible to your member (an RFQ is never visible outside the requester's Market Segments eligibility), or is no longer open: accepted, cancelled, or past its quoting window. Cancelling an RFQ your member did not create is also 2024. | Treat as closed. Refresh GET /v1/rfq/open or GET /v1/rfq/mine and act on live state; do not retry the same id. |
| 2025 | RFQ_QUOTE_NOT_FOUND | 404 | The quote does not exist on that RFQ, was withdrawn or replaced by its maker, has passed its validity window, or its maker can no longer trade with you at accept time: suspension, liquidation, default management, or a Market Segments change since the quote arrived. | Accept another live quote or wait for a fresh one. Quote validity defaults to 30000 ms, so accept promptly. A maker holds one live quote per RFQ: a re-quote replaces the earlier quote and retires its quoteId. |
| 2026 | RFQ_SELF_TRADE | 400 | Quoting an RFQ your own member opened. The check is member-scoped and stricter than the order book's end-client STP default: two desks or two end clients of one member cannot face each other by RFQ. Because your own quote can never exist on your own RFQ, accepting one is impossible by construction. | Nothing to fix venue-side: an RFQ trade is bilateral and one member cannot be both parties. Do not retry unchanged. |
| 2027 | BARRIER_TERMS | 400 | Barrier terms that do not fit the market and side. On a barrier market barrier is required, barrier.level must sit on the knock-out side of the current mark (below it for BUY, above it for SELL), barrier.expiryTs, when present, must be in the future, and barrier.takeProfit, when present (revision 3.43), must sit strictly on the profit side of the current mark (above it for BUY, below it for SELL). On a perp or spot RFQ market any barrier at all is 2027. A maker sees 2027 when its quoted entry price would sit at or past the requester's barrier or at or past the take profit, or when the contract's implied maximum loss on either side, entry-to-barrier (or entry-to-take-profit) distance x qty x contract value, would exceed the venue's exact-money range (1000000000000000000 settlement-currency units; not reachable at any tradable size). On a close request (revision 3.44) the writer's price must lie strictly inside the contract's terms. A STRICT take-profit request (revision 3.45, barrier.strictTakeProfit true) is 2027 without a takeProfit, without an expiryTs, with an expiryTs more than 24 hours after the request, or on a market whose strictTakeProfitPremiumBps is 0 (not offered), at create and again at accept. Since revision 3.70, on a market that publishes maxContractLifeHours on GET /v1/instruments, an OPENING request whose barrier.expiryTs is absent or later than the request time plus that many hours (a rule of the market, measured at the request; close requests untouched, open contracts run to their terms), and, where the market publishes barrierExpiryRequired true, an OPENING request without barrier.expiryTs (refused at the gateway before anything is journaled). | Fix the terms. BUY means the requester holds long with the knock-out below the entry; SELL means short with the knock-out above. Omit expiryTs for an open-ended contract only on a market that publishes no maxContractLifeHours; where one is published send an expiryTs at most that many hours after the request; for a strict take profit send both a take profit and an expiry within 24 hours on a market that offers it. |
| 2028 | BELOW_MIN_NOTIONAL | 400 | The order or RFQ notional (price x qty) is below the instrument's minNotional (5 USD on every current listing, 100 USD on the barrier markets, a close request included: the request is valued at the contract's whole quantity at the current mark and the writer's close quote at its price, so a contract below the floor ends at its barrier, its take profit or its expiry and not by an early close; revision 3.71). Priced orders are valued at the limit price (conditional limit types at the post-trigger limit price), market-type orders at the current mark. Amends re-check the FULL amended quantity at the effective price. RFQs are checked at create (valued at the current mark) and on every quote (valued at the quoted entry price); accepts need no further check. Exempt: venue-initiated closes (liquidation, ADL) and reduceOnly orders and amends within your reducible position. | Increase the quantity so price x qty reaches minNotional. To flatten a sub-floor residual position, send a reduceOnly order of exactly the position size: reduce-only within your reducible position is exempt from the floor, while an oversized reduce-only rejects 2008. Do not retry unchanged. |
| 2029 | CLIENT_LIMIT | 400 | The venue's table of end-client identifiers is full, so this order or RFQ acceptance would have to register a clientUid the venue has NEVER seen. Orders and acceptances quoting an identifier the venue already knows are unaffected: only the first-ever use of a new identifier can hit this. The bound is 1,048,576 distinct identifiers venue-wide, against roughly 50,000 in use across all members, so it is not reachable at any realistic client count. Nothing is deleted to make room: an identifier carries lifetime realized PnL and funding for that end client, so the table is capped, never pruned. | Send the identifiers you actually use rather than a fresh one per order: clientUid is the ultimate client's stable reference, clOrdId is the per-message one. If your identifiers are correct and you still see this, contact venue operations. Do not retry unchanged. |
| 2030 | CLIENT_IDENTITY_INVALID | 400 | An RTS 22 identity submission (PUT /v1/clients/{clientUid}/identity, REST page) failed validation: type not NATURAL_PERSON or LEGAL_ENTITY, a required field missing for the client type, a field on the wrong client type (lei is LEGAL_ENTITY only; dateOfBirth, nationalId and nationalIdType are NATURAL_PERSON only), countryCode not ISO 3166-1 alpha-2, dateOfBirth not a valid YYYYMMDD, identifierPriority outside 1 to 3, nationalId and nationalIdType not set together, a length bound breached, or an lei failing the ISO 17442 format or MOD 97-10 checksum, or, for a natural person without nationalId, a firstName or surname with no letter the ESMA CONCAT transliteration table can fold (a name written only in Greek or Cyrillic, for example: resubmit the names in transliterated Latin form or supply nationalId and nationalIdType). Also a path clientUid outside 1 to 64 characters. Since revision 3.53: in the ENFORCE stage of the RTS 22 Annex II rollout (production: in force from the venue's first day; sandbox: from Tuesday 2026-09-22, the WARN stage before that date reporting the same findings in the PUT echo warnings array instead), a nationalIdType the country does not designate, an identifierPriority that contradicts the Annex II table, a national identifier failing the country's format check, or a natural person without nationalId for a country whose row lists no CONCAT (EE, ES, IS, IT, MT, PL). The same code answers the trader identity route (PUT /v1/trader-ids/{traderId}/identity): kind not PERSON or ALGO, a PERSON without branchCountry, an ALGO without a well-formed algoId or with person fields. | Fix the named field; the msg names it. Nothing was stored and the previous revision, if any, stands. Do not retry unchanged. Trading is unaffected: identity data is decoupled from order entry. |
| 2031 | CLIENT_IDENTITY_NOT_FOUND | 404 | GET /v1/clients/{clientUid}/identity for a clientUid with no identity record on file, or (revision 3.53) GET /v1/trader-ids/{traderId}/identity for a Trader ID with no identity on file. | Expected until the record is first submitted. PUT the record, then re-read; use GET /v1/clients/identity/missing to find every uncovered clientUid that traded in the window. |
| 2032 | UNKNOWN_LOT | 404 | POST /v1/lots/{lotId}/close (REST page, Lots) named a lotId that is not an OPEN lot of the given clientUid and symbol: never opened, already fully closed, consumed by a venue-side close (the lot view is clamped to the live sub-position), or belonging to a different client. Nothing was submitted. | Re-read GET /v1/lots for that clientUid and symbol and act on the lots it actually returns. The view folds from the history store, so a fill acked a moment ago may not be reflected yet; poll the watermark forward rather than retrying the same lotId unchanged. |
| 2033 | TRADING_CAPACITY | 400 | Trading capacity enforcement is at the ENFORCE stage for your member and the order, RFQ creation or RFQ quote violated it: reg.tradingCapacity absent, not exactly DEAL, MTCH or AOTC, not permitted by your licensing profile, DEAL with a clientUid that is neither absent nor your house reference, or MTCH/AOTC without a real end-client clientUid. On FIX also an OrderCapacity(528) value with no venue mapping (only P, G, R, A map) or one contradicting tag 9021. Decided at the gateway; nothing reached the engine. Members at stage OFF or WARN never receive this code. | Send the capacity your licensing profile permits with the matching clientUid shape, or contact venue operations if the permitted set looks wrong. Do not retry unchanged. On production, enforcement is at the ENFORCE stage from your first order; in the sandbox it is staged per member (off, warn, enforce) and begins only when venue operations agree the stage with you; your current stage shows on the member portal Regulatory tab. |
| 2034 | NO_LICENSING_PROFILE | 403 | PRODUCTION only (fail-closed posture): your member has no licensing profile configured, so the venue refuses order acceptance on every surface (order entry, batch and lot close, order amend on REST, WebSocket and FIX, RFQ create, quote and accept; on FIX a 35=G answers 35=9 with 9010=2034) until venue operations configure one. Profile configuration is a mandatory onboarding artifact. The sandbox runs fail-open and never answers this code. | Contact venue operations to complete onboarding. Not retryable: nothing you send changes the answer until the profile exists. |
| 2035 | CLOSE_CONTRACT | 400 | A barrier CLOSE REQUEST (POST /v1/rfq with closeContractId, revision 3.44) named a contract that is not an OPEN barrier contract YOU HOLD on symbol: never opened, already settled, held by another member, written rather than held by you, on a different market, a zero or negative id, or a contract that already has a close request open (one at a time). Also answered on the accept of a close request when the contract terminated between quote and accept (a belt: the termination cancels the request in the same command, so the accept normally finds no RFQ, 2024). Nothing was submitted or journaled on the create; nothing traded on the accept. | Re-read GET /v1/barriers (role HOLDER, status OPEN) and use a contract id it lists; if a close request of yours is already open on it, work with that one under GET /v1/rfq/mine or cancel it first. Do not retry unchanged. |
| 2036 | SWAP_TERMS | 400 | A yield contract OFFER (POST /v1/rfq on a SWAP market, revision 3.51) names terms the market does not admit: rateBps outside 1 to swapMaxRateBps, tenorDays outside swapMinTenorDays to swapMaxTenorDays, qty below minNotional (the minimum take, in the asset) or above maxOrderQty, minQty above qty, expiresInMs outside 60 s to 30 days, or a size whose principal plus reward would leave the exact-money range (a zero or off-grid qty / minQty is 2002 before any of this). Nothing was posted. | Read the market's bounds from GET /v1/instruments and repost inside them. |
| 2037 | SWAP_OFFER_ONLY | 400 | Swap markets have offers and takes, never quotes: a quote against a swap offer, or a swap accept carrying a quoteId. Nothing was submitted. | To provide, post your own offer; to take, accept with qty and no quoteId. |
| 2038 | SWAP_SIZE | 400 | A swap TAKE named a size the offer does not admit: above the offer's remainingQty (a zero or off-grid qty is 2002 instead), above YOUR maxOrderQty, or below the offer's minimum take (the larger of the writer's minQty and the market's minNotional) without being the whole remainder. Nothing traded. | Re-read the offer on GET /v1/rfq/open and take a size inside the rule; the whole remainder is always admissible. |
| 2039 | QUOTE_OFF_MARKET | 400 | The accept of an OPENING barrier request named a quote whose entry price lies more than the market's published band from the CURRENT mark (barrierGuard.entryBandBps on GET /v1/instruments, 50 bps unless the market sets its own; revision 3.70, the registry sentence names the field), or the mark itself is not live (no sequenced mark, or older than the market's staleness timeout, 60 s on the current barrier listings). Revision 3.55; in force on every environment. Never answered on an early-close accept. Nothing traded; the request stays open and the quote stays open. | Wait for fresh quotes (quoting members should refresh when the mark moves) or re-request; on a stale market wait for the mark to resume ticking. Do not retry unchanged. |
| 2040 | TAKE_PROFIT_REACHED | 400 | The accept of an OPENING barrier request carries a takeProfit the CURRENT mark has already reached: BUY with the mark at or above the level, SELL at or below (revision 3.55; in force on every environment since revision 3.70). The contract would terminate AT the take profit on the first tick after opening. Nothing traded; the request stays open and the quote stays open. | Re-request with a takeProfit beyond the current mark, or accept a fresh quote after the mark settles. Do not retry unchanged. |
| 2041 | TAKE_PROFIT_TOO_CLOSE | 400 | The accept of an OPENING barrier request carries a takeProfit closer to the accepted entry price than the market's barrierGuard.minTakeProfitBps of the entry (GET /v1/instruments; 10 unless the market sets its own; revision 3.55; in force on every environment since revision 3.70; a distance exactly at the floor passes). Nothing traded; the request stays open and the quote stays open. | Re-request with the takeProfit at least the market's published floor away from the entry (0.1 percent of the entry unless the market sets its own). Do not retry unchanged. |
| 2042 | BARRIER_TOO_CLOSE | 400 | The accept of an OPENING barrier request carries a barrier closer to the accepted entry price than the market's barrierGuard.minBarrierBps of the entry (GET /v1/instruments; 10 unless the market sets its own; revision 3.55; in force on every environment since revision 3.70; a distance exactly at the floor passes): a near-immediate knock-out that would forfeit the holder's premium on the first tick of noise. Nothing traded; the request stays open and the quote stays open. | Re-request with the barrier at least the market's published floor away from the entry (0.1 percent of the entry unless the market sets its own). Do not retry unchanged. |
| 2043 | BARRIER_REACHED | 400 | The accept of an OPENING barrier request carries a barrier the CURRENT mark has already reached or crossed: BUY with the mark at or below the level, SELL at or above (revision 3.55; in force on every environment since revision 3.70). The loss-side mirror of 2040: the contract would knock out on the first tick after opening, forfeiting the holder's premium and paying entry fees on the full notional. Nothing traded; the request stays open and the quote stays open. | Re-request with a barrier beyond the current mark, or accept a fresh quote after the mark settles. Do not retry unchanged. |
| 2044 | PREMIUM_SHARE | 400 | A barrier quote on an OPENING request carries a premiumBps, the writer's OWN premium share for THIS quote (revision 3.57), outside the allowed range: at least the market's effective share for the request's variant (normal: 10000 - knockOutRebateBps; strict: strictTakeProfitPremiumBps) and at most 10000. A writer can only make a contract MORE expensive than the market base, never cheaper; on a close request or a non-barrier market the field is answered 2013 instead. Nothing was submitted. | Re-quote with premiumBps inside the range (read the market's shares on GET /v1/instruments), or omit it for the market share. |
| 2045 | CLORDID_COLLISION | 400 | Your clOrdId on an order or a swap take hashes to the same 64-bit value as a DIFFERENT clOrdId you still have live in the venue's idempotency window (revision 3.59; dormant until venue operations announce activation). The venue refuses the new command rather than displacing the stored reference, so a later retry of the earlier one still resolves correctly. Astronomically rare (a 64-bit hash collision within your own live references). Nothing was submitted or journaled. | Re-send with a different clOrdId. The identical-payload retry path (2009) is unaffected. |
| 2046 | AUCTION_PHASE | 400 | An order type the market's trading phase does not admit (revision 3.67, the call auctions of SECURITY markets): an IOC, FOK, MARKET or conditional order during the opening or closing call or while the market is CLOSED. GTC and GTD LIMIT orders rest for the next uncross; cancels and amends work in every phase. Nothing was booked. | Read tradingPhase on GET /v1/instruments or follow status.{symbol} (nextPhaseTs); send the order as a GTC or GTD LIMIT to take part in the uncross, or wait for CONTINUOUS. |
| 2049 | TOKEN_RESTRICTED | 400 | An order, an amend, an RFQ request or a quote on a SECURITY market whose DLT financial instrument is frozen on the Kronos X chain, or for a client (clientUid) whose custody wallet there is frozen by the venue registrar (member reference section 6i, revision 3.70): a measure with a ground and a case reference recorded on the ledger and served to venue operations only. Answered before anything is sequenced, on REST, the WebSocket ack and the FIX report (9010=2049) alike; the client's resting orders on SECURITY markets were cancelled when the wallet freeze was taken, a frozen instrument's markets are halted. Your other clients and markets are not touched; nothing was placed. | Route no flow for that client or instrument until venue operations lift the freeze (the wallet reads status Frozen on GET /v1/chain/wallets, the instrument frozenBy on GET /chain/v1/tokens/{isin}); contact venue operations for the measure and its appeal route. |
3xxx, risk and trading state
| Code | Name | HTTP | Returned when | What you do |
|---|---|---|---|---|
| 3001 | INSUFFICIENT_MARGIN | 400 (usually a rejectCode) | Perps: the pre-trade margin check fails against worst-case order margin, evaluated at the collar price for MARKET orders. Spot buys: free settlement cash cannot prefund the worst case. Also returned on withdrawals exceeding withdrawable equity. | Reduce size, add cash, or free margin by cancelling working orders on the same side. |
| 3002 | MEMBER_STATUS_FORBIDS | 403 | The member is SUSPENDED, is in liquidation or default management (order placement, amends and withdrawals are all refused while riskStatus is LIQUIDATION or DEFAULTED; cancels stay accepted), or the action conflicts with a POST_ONLY or REDUCE_ONLY member session. | Contact venue operations. Watch riskStatus on /v1/account and the venueEvents channel. |
| 3003 | INSTRUMENT_HALTED | 400 | The instrument is HALTED: circuit breaker, administrative halt, or venue halt propagation. TEMPORARY. | Pause flow on that symbol and resume on the RESUME_TRADING venue event. Do not spin on retries. |
| 3004 | INSUFFICIENT_ASSET | 400 | A spot SELL exceeding the free base-asset balance, which is balance minus open sell reservations. Also a swap TAKE (revision 3.51) whose principal plus taker fee exceeds what you can pay in the market's asset right now (your free asset balance, and on the USD swap no more than your available margin balance). | Deposit the asset or cancel competing sells. Read free, not total, from your asset balances; on a swap take, reduce qty. |
| 3005 | NO_MARK_PRICE | 400 | The instrument has no sequenced mark price yet: newly listed, or the price feed is not live. This is NOT a halt. | Wait for the feed and poll GET /v1/mark-price. No resume event will arrive, because nothing was stopped. |
| 3006 | INSTRUMENT_DELISTED | 400 | The instrument is DELISTED. PERMANENT for that listing: after a re-listing (member reference section 2a; a tickSize or qtyStep change is never made in place) the same symbol can return as a new market with its own grid, under the symbol and isin the announcement names. | Stop trading the symbol and flatten per venue instructions. Remove that listing from your quoting universe; it will never resume. Re-read GET /v1/instruments after the date a re-listing announces. |
| 3007 | COUNTERPARTY_MARGIN | 400 | On POST /v1/rfq/{id}/accept: the QUOTING member no longer passes the venue's pre-trade checks for this trade, margin or position limits, evaluated at the quoted price at accept time. Your own margin failing the same accept is 3001; 3007 is the other side of the trade failing. On a swap TAKE (revision 3.51) it means the WRITER is no longer fit (suspended, reduce-only, in liquidation or default, blocked by an outstanding claim), in which case the offer is WITHDRAWN in the same command, or at its open-swap or contract-count caps for your size, in which case the offer STAYS on the board. | Not your configuration and not fixable on your side. Accept a different quote or wait for fresh ones; the RFQ stays open for the rest of its quoting window. On a swap market re-read the board: a withdrawn offer is gone, an offer at the writer's caps can be tried smaller or later. |
| 3008 | SWAP_WRITER_BLOCKED | 400 | You posted a swap OFFER while a contract you WROTE carries an outstanding claim: a maturity or final settlement the venue could pay only in part from your balance in the asset (revision 3.51). Derived from your live contracts, not a member flag: it lifts on its own when the venue's timer pays the last claim to zero. Nothing was posted. | Fund the asset (the venue retries the claim every second) and repost once GET /v1/swaps shows no DEFAULTED contract with you as WRITER. |
| 3009 | MARKET_ACCESS | 400 | Your account may not open NEW risk on this market (revision 3.61): the market requires admission (GET /v1/instruments accessMode ADMISSION) and you are not admitted, or venue operations set your access to it to DENIED (a measure with a rulebook ground, notified to you by mail with the appeal route). Reduce-only orders and barrier close requests stay admitted; open positions and contracts run to term. The six swap markets require admission under the SWAP_COUNTERPARTY class of the Rules annex (member reference section 6h, revision 3.70: the client category recorded for the member, the Swap Product Annex, conformance testing under Conformance Test Plan Section W; both roles, granted per market); every perpetual, spot and barrier market is OPEN. | Read GET /v1/account/parameters markets[].access. Reduce only, or apply for admission under the market's published criteria (a DENIED measure appeals under Rules section 36; a refused admission goes to the complaints procedure of section 38). |
| 3010 | DEA_CLIENT_BLOCKED | 400 | The clientUid or reg.traderId of this reg.dea order (FIX 9026=Y) is an identifier venue operations STOPPED on your account (member reference section 4, revision 3.62): a measure against your provision of direct electronic access to ONE client (MiFID II Article 48(7), Rules sections 8 and 9), taken with a stated Rules ground and a case reference and notified to you by mail with the section 36 appeal route. Answered before anything is sequenced, on REST, the WebSocket ack and the FIX report alike. Your unmarked flow and the DEA flow of your other clients are not touched; the identifier's working orders are not cancelled by the venue. | Cancel that identifier's working orders; route none of its flow until the stop is lifted; appeal under Rules section 36. Read GET /v1/account/parameters account.dea.stoppedIdentifiers. |
| 3011 | DEA_FLOW_LIMIT | 400 | Your DEA-flow allowance for the calendar day (UTC) is reached: the count or the notional of reg.dea orders your published DEA class allows (revision 3.62). Only reg.dea orders count and only they are refused; the allowance resets at 00:00 UTC. | Wait for the reset, or ask venue operations for a class whose criteria you meet. Read account.dea (limits, usedToday) on GET /v1/account/parameters. |
| 3012 | LEDGER_FINALITY_PENDING | 400 | A movement OUT of a trading wallet on the Kronos X chain (POST /v1/chain/transfers, POST /v1/chain/unlock) was refused because the validator quorum of the latest block is not reached yet (an unattested tail, revision 3.66). Trading, fills and their settlement obligations are unaffected; only movements out of trading wait. Nothing was debited. | Retry when GET /chain/v1/head shows unattestedTail: false, typically within seconds; a tail that stays unattested is a venue incident and operations will announce it. |
| 3013 | SWAP_WRITER_ROLE | 400 | You posted a swap OFFER but your account is not designated as a Market Maker Member (member reference section 6h, revision 3.70), or the offer carried a clientUid other than your house uid (a Market Maker Member writes on its own account only). The designation is granted under the Rules with a signed Market Maker Agreement and recorded on your onboarding record; a withdrawn designation is a Rules measure notified to you with the section 36 appeal route, and your standing offers are then withdrawn at the next take (3007). Answered before the market's terms are checked, once GET /v1/instruments swapWriterEligibility reads MARKET_MAKER on the market (while it reads ANY every member may write). Nothing was posted. Taking offers is unaffected. | Take offers, or apply for the Market Maker Member designation under the Rules; a Market Maker Member re-sends the offer without clientUid or with its house uid. |
| 3014 | FUNDING_CATCH_UP | 400 | This GRID perpetual's funding settlement is more than 60 s overdue and the venue is settling the missed anchors (member reference section 7, revision 3.70): orders, amends and RFQ acceptances that would open or increase exposure are refused until the catch-up lands; reduce-only orders and amends stay admitted; a conditional order that triggers inside the window is canceled with cancelReason FUNDING_CATCH_UP. Uniform per market, never member-specific. | Retry after the catch-up lands: the next FUNDING_SETTLED event on the funding channel, or GET /v1/funding/current until nextFundingTs lies in the future; normally within seconds of the venue resuming. |
RESUME_TRADING event, so wait and watch. 3005 has no state to change: the instrument is fine, the feed simply has not produced a sequenced mark yet, so poll the mark price. 3006 is permanent for that listing and no event will ever release it, so a retry loop on 3006 burns your rate limit until the key is throttled; a re-listing brings the symbol back as a NEW market, announced in advance. If your reconnect logic treats all three the same, it will either hammer a delisted symbol forever or give up on a halted one that comes back in ninety seconds.4xxx, concurrency
| Code | Name | HTTP | Returned when | What you do |
|---|---|---|---|---|
| 4001 | VERSION_CONFLICT | 409 | A Market Segments update carried a stale expectedVersion: someone changed the configuration between your read and your write. | Re-read the current configuration, reapply your intended change on top, resubmit. Never blind-overwrite: the other writer may be your own risk desk pulling a counterparty. |
5xxx, venue and system
| Code | Name | HTTP | Returned when | What you do |
|---|---|---|---|---|
| 5000 | INTERNAL_ERROR | 500 | An unhandled server-side error. Logged venue-side against the request route. Since Member API revision 3.74 also the ROW of a batch entry (placement, amendment or cancellation) whose outcome the venue could not confirm, msg "outcome unknown: the venue could not confirm whether this entry took effect; read the order back on the leader before sending it again", inside a 200 / code: 0 answer whose other rows stand. Since Member API revision 3.83 also the WebSocket ack of a write op whose commit the venue did not confirm within its 10 second window on a clustered deployment, with msg internal error, the answer REST gives the same case with HTTP 500; before that revision the WebSocket acked it 2013 malformed request. The outcome of such a write is unknown: it may still execute. Two other venue-side faults still ack 2013 malformed request on a single WebSocket write op, where REST answers this code: a node whose cluster engine has stopped on a fault refuses the write before it enters the venue's log, and a write still waiting when a node's apply loop stops fails with its outcome unknown; a 2013 malformed request on a write you know to be well formed can be either, so reconcile it by clOrdId before you resubmit. | Retry idempotent reads. For order entry, RECONCILE by clOrdId before resubmitting: the command may or may not have reached the engine. Report persistent 5000s to operations. On a batch row, read the order back on the leader before sending it again, and resend a placement entry only with the SAME clOrdId. After a write answered 5000 on REST or the WebSocket, read the order back on the leader by clOrdId, or send it again with the SAME clOrdId, before you decide. |
| 5001 | ENGINE_BUSY | 503 | The command queue is saturated. The command was NOT accepted and nothing was journaled. One exception on a clustered deployment (member reference section 10.7, Member API revision 3.83 states it): a SINGLE write, REST or WebSocket, answered 5001 while its node lost the leadership may already be in the venue's log and may still execute on the new leader at its original terms; a batch row's 5001 never is such a write. On a batch entry (Member API revision 3.74): that entry was not accepted, or was not submitted because the venue's intake refused an earlier entry of the same batch as it was submitted; its earlier neighbours may have taken effect. On a clustered deployment the replicated intake can refuse an entry after the later ones were submitted, so its later neighbours may have taken effect too: read every row, never infer the rest of the batch from the first 5001. At the envelope of an amendment or cancellation batch also: the venue could not look the orders up, or eight of your cancellation batches already wait on the node; nothing was submitted. | Retry with backoff and jitter, reusing the same clOrdId. Outside a leader change this one is definitively safe to retry, which is why it is a distinct code from 5000. After a batch, resend only the entries answered 5001. During a leader change, read a single write answered 5001 back on the leader by clOrdId before resending it, or resend it with the SAME clOrdId. |
| 5002 | VENUE_HALTED | 400 | The whole venue is halted. | Pause. Watch venueEvents with scope: "VENUE" and GET /v1/status. |
| 5003 | INVALID_PARAMETERS | 400 | Structural parameter errors on backfill and administrative surfaces: for example supplying both afterCursor and fromSeq on /v1/stream/events, or neither, or toSeq below fromSeq. msg names the parameter. Since Member API revision 3.73, also a channels value on GET /v1/stream/events that names no family (an empty value, commas only): msg is channels must name at least one family. Since the same revision, also on every route a required query parameter that is absent, or a parameter value that does not parse as its documented type (an integer, true or false): msg names the parameter (symbol is required, limit must be a 32-bit integer, includeSkips must be true or false); before it such a request answered HTTP 400 with an empty body. Since Member API revision 3.84, also on DELETE /v1/orders a query parameter other than clOrdId, symbol and side (unknown parameter 'clientUid': DELETE /v1/orders reads clOrdId, symbol and side only; nothing was cancelled), one of them given twice, clOrdId with symbol or side, and a side other than BUY or SELL (side must be BUY or SELL; nothing was cancelled), on that route and on the WebSocket order.cancelAll alike, and the empty orderId of DELETE /v1/orders/ with a trailing slash, with or without a query (orderId is empty: DELETE /v1/orders/ with a trailing slash names no order; send DELETE /v1/orders/{orderId} with the id, or DELETE /v1/orders without the slash to cancel all; nothing was cancelled); nothing is cancelled, where before such a request without clOrdId ran as a cancel of every open order the other filters matched (a repeated symbol excepted, which answered 2001), and one with clOrdId cancelled at most the order clOrdId named. | Fix the call. Distinct from 2019, which means the cursor or channel VALUE was bad rather than the parameter combination. |
| 5004 | BACKFILL_UNAVAILABLE | 503 | This deployment runs without the durable event store, so /v1/stream/events, /v1/stream/cursor and /v1/margin/calls cannot answer. Authentication still runs first, so an unauthenticated caller learns nothing about the deployment. On the event stream routes the same HTTP 503 also carries 5007, which is retried: branch on the code. | Use snapshot reads plus dated history routes. Confirm environment capabilities with operations before you build a recovery path on cursors. |
| 5005 | COMMAND_FAILED | 500 (usually rejectCode) | The venue accepted your command and durably recorded it, then could not finish processing it. Rare, and always a venue-side fault rather than anything wrong with your request; the venue stays up and keeps trading. On order entry it arrives like every other engine rejection, inside a 200 / code: 0 response as data.rejectCode, on the WebSocket order channel, and on FIX as 9010=5005. What makes it different from every other rejection: the rest of data reports what the command DID achieve before it stopped, so an orderId there is a real order that may be resting or partially filled, and any executions listed really happened. On FIX the code rides an ExecType(150)=I restatement of that real order, carrying its own OrderID(37), OrdStatus(39), LeavesQty(151) and CumQty(14), and a terminal 150=8 only when nothing was created. Since Member API revision 3.74 a batch amendment or cancellation answers it per entry: the order's row with rejectCode 5005 when the entry took effect before the fault, a failure row with code 5005 when nothing took effect. | Do NOT resubmit the identical request (a batch row carrying 5005 included: reconcile it before any resend and never resend it as it was): the venue is deterministic, so it fails the same way every time. Reconcile first against the response's own orderId and executions plus GET /v1/orders/open and GET /v1/positions, and cancel anything you did not want. Then send a DIFFERENT request if you still need the position, and report the clOrdId and the response timestamp to venue operations. A repeat on the same market is a reason to pause and call operations. Since Member API revision 3.79, ten 5005 answers of yours within 60 seconds turn every request of yours that consumes the order budget into 1003 until the oldest of them is 60 seconds old (Member API section 4); cancellations stay open, and so do the requests that can only reduce your risk (a reduce-only order, a batch of reduce-only entries only, a lot close sent reduce-only, a barrier early close). |
| 5006 | LEDGER_UNAVAILABLE | 503 | A chain route was called while the chain host runs without its engine bridge: a venue-side configuration fault, never a member error (revision 3.66). Nothing happened. A venue that does not run the Kronos X chain at all does not serve the /v1/chain/* and /chain/v1/* routes, which answer HTTP 404 there, not this code. | Retry after a minute; if it persists, contact venue operations. On the venues that list SECURITY markets on GET /v1/instruments the routes are always served. |
| 5007 | HISTORY_STORE_BUSY | 503 | A read served from the venue's history store (the event stream routes of member reference section 11 and the history routes of its section 4) did not complete within the venue's bound, a read of yours of the same group just exceeded it (the group is held), the store is catching up with the venue's writes, or the store did not answer. Nothing changed on your account. Since revision 3.73; before it the same failure answered 500 with 5000, and a read slower than the bound answers this code where it was served slowly before. | Sleep data.retryAfterMs (also Retry-After), then retry the same read; back off exponentially if it repeats; after repeated 5007 on the same read, NARROW it (a smaller limit, a later cursor or from, no symbol); keep one read in flight per stream. |
| 5030 | NOT_LEADER | 503 | Clustered deployments only: a request that is not a GET under /v1 (a read-only POST such as POST /v1/margin/preview included) reached a node that is not the leader, which answers it before the route runs and never forwards it. The write was refused before it reached the engine, so nothing was journaled, the order does not exist and it cannot reach the book later. The body carries leaderId and leader, the member-facing https base URL venue operations configure for the leader, which on every venue deployment is the environment's published entry host (member reference section 1), never a node's own host name and never a WebSocket URL; while no leader is known REST answers leaderId 0 with leader null and the WebSocket ack carries leaderId 0 and no leader. A node that is not the leader closes every HTTP connection after its answer, reads included (Connection: close on HTTP/1.1, GOAWAY on HTTP/2; over HTTP/2 it first reads the rest of a write's body, for at most 2 s), but does not close a WebSocket opened on it. Since Member API revision 3.74 the WebSocket ops orders.batchAmend and orders.batchCancel ack it too on a follower, before anything is charged or counted: success false, msg NOT_LEADER, with leaderId and leader under data rather than at the top level; since Member API revision 3.76 every other WebSocket write op does the same on a follower (order.place, order.cancel, order.cancelAll, order.amend, orders.batch, marketSegments.set, which acked 5001 there before, and session.cancelOnDisconnect, which acked armed; answered first, ahead of authentication and validation). A single-node deployment never emits this code; the clustered sandbox at sandbox.mtf.perpetuals.net (member reference section 1a, from the date venue operations announce) emits it at a leader change (member reference section 10.7 states the rest since Member API revision 3.83). | Treat it as a redirect, never as a rejection. Accept leader only when it equals the entry host you configured for the environment, then send the request again there on a NEW connection, reusing the SAME clOrdId; if leader names any other host, send no credentials there, reconnect to your configured entry host and tell venue operations; if it is null or absent, back off briefly and retry your entry host. On a WebSocket op, close the socket yourself at the first 5030 (the venue does not close it), reconnect to the entry host, auth, resubscribe, re-arm cancel-on-disconnect and send the op there. A cancel, cancel-all, amendment or cancellation batch answered 5030 did not take effect: send it again on the leader at once. This is not a rate limit and not a backoff case: repeating the request against the same node fails identically until that node is the leader. Reads and streams are still served by a follower, so only your write path needs to move. |
WebSocket close codes
Close codes are a separate namespace and never appear in a code field. 1000 is the standard WebSocket normal closure and 1001 (going away) is what a venue restart sends (and, while the venue keeps running, what a venue-side fault that lost frames of one command or batch sends); the venue's own close codes live in the 4000 to 4099 private-use block.
| Close | Name | Meaning | Reconnect guidance |
|---|---|---|---|
| 1000 | NORMAL | You closed the socket (standard normal closure). The venue does NOT use 1000 for its own shutdown: a venue stop arrives as 1001. | Reconnect normally if you still want the stream. Nothing was lost. |
| 1001 | venue restart | Normally the venue is stopping for a rollout or a restart (revision 3.45). One advisory control frame ({"channel":"advisory","type":"advisory","advisory":"restart",...}) precedes the close, guaranteed frames already released to the socket are written first, and new WebSocket upgrades answer HTTP 503 with Retry-After while the venue is stopping. Since Member API revision 3.79 the description also names the second cause: the venue sends the same advisory and close (FIX: Logout(35=5) with Text(58)="venue restart"), while it keeps running, to every session that may have missed a frame when a venue-side fault lost frames of one command or batch (the command itself executed); the venue keeps running, so new upgrades are not refused; the cursor backfill delivers every missed frame of the guaranteed families, and for the best-effort families and for rfq, barriers and venueEvents you re-read state (member reference section 10.8); the venue adds no other close code or reason for it. On a clustered deployment (among them the clustered sandbox of Member API revision 3.83, from the date venue operations announce) the same close with reason leader change, reconnect, after the advisory leaderChange (revision 3.76), means the node you were connected to no longer leads; the venue stays up and new upgrades are not refused. | Reconnect with backoff, auth again, resubscribe (subscriptions are not preserved), re-arm cancel-on-disconnect if you use it, then recover as after any disconnect: cursor backfill for the guaranteed families, state re-read for rfq and barriers. Not an error on your side. |
| 4008 | SLOW_CONSUMER | You stopped reading and a guaranteed queue or the request/reply queue overflowed. A terminal gap frame naming the affected families is sent immediately BEFORE the close. | Wait at least 1000 ms. Reconnect, resubscribe, then backfill from the gap frame's cursor via GET /v1/stream/events. Then fix your consumer: the venue closed you rather than silently drop guaranteed events. |
| 4009 | IDLE_TIMEOUT | No INBOUND frame from you for 60 seconds. The server's own heartbeats do not count: you must send something. | Reconnect and add a keepalive ping at 30 seconds or less. Nothing was dropped by an idle close. |
fromSeq and fromEventIndex are the last cursor the session SUCCESSFULLY WROTE to the socket, inclusive, not the frame the full queue refused. Resuming from anywhere later is silent data loss. The frame's backfill.query is pre-built by the venue so it can never hand you a query it would itself reject, and unrecoverable lists any guaranteed family with no durable row source, which you must recover by state re-read instead.Enumerated string values
Order entry
| Field | Values |
|---|---|
side | BUY, SELL |
type | LIMIT, MARKET, STOP_MARKET, STOP_LIMIT, TAKE_PROFIT_MARKET, TAKE_PROFIT_LIMIT. No iceberg, no trailing stop, no pegged orders: anything else is 2004. |
tif | GTC (also the default when omitted), IOC, FOK, POST_ONLY, GTD |
trigger.source | LAST, MARK, INDEX |
trigger.direction | AT_OR_ABOVE, AT_OR_BELOW |
Order status
status: NEW, PARTIALLY_FILLED, FILLED, CANCELED, EXPIRED, REJECTED, PENDING_TRIGGER.
PENDING_TRIGGER is a conditional order held off-book until its trigger fires; it is not in the book and does not appear in depth. EXPIRED is reserved for GTD expiry: everything else terminal-by-cancel is CANCELED with a cancelReason.
NEW is a working order with nothing filled; PARTIALLY_FILLED a working order with fills, whether it part filled on entry or its resting remainder was later consumed by an aggressor. Since Member API revision 3.60 the live state reads (GET /v1/orders/open, GET /v1/orders/{orderId}, WS get.openOrders and get.order, the FIX 150=I restatement) report the latter case as PARTIALLY_FILLED; before, they reported NEW with remainingQty below qty, and the sandbox keeps doing so until venue operations announce the activation of that rule. Event payloads always derived the status from the fill.
Cancel reasons
cancelReason is a closed set of fifteen values (revision 3.70 added FUNDING_CATCH_UP and POST_ONLY_WOULD_CROSS). "Venue-initiated" means the venue acted on its own; you did not ask and your order's own terms did not require it.
| Value | Venue-initiated | Emitted when |
|---|---|---|
USER_REQUESTED | no | You cancelled it: a cancel of yours on any transport, a member-wide cancel-all of yours (order.cancelAll, or DELETE /v1/orders without a symbol, from any session of yours), the member portal's kill switch, and the cancel on disconnect you armed (member reference section 10.5a), which reports this value by design because the armed protection is your own standing instruction; the value does not tell these apart (Member API revision 3.83 states it). ADMIN_CANCEL is reserved for cancels by venue operations. |
IOC_REMAINDER | no | The unfilled remainder of an IOC after the sweep. |
FOK_UNFILLABLE | no | An FOK could not fill completely inside the collar. Nothing traded. |
GTD_EXPIRED | no | A GTD order reached its expireTs. Surfaces with status: "EXPIRED", not CANCELED. Since Member API revision 3.86 such an order never trades at or after expireTs: an order, an amendment, a cancellation, a trigger or a call auction's uncross that reaches it from that instant expires it, and the venue's expiry sweep, about once a second, expires one nothing reaches. The matching engine applies this from its replay-contract line for revision 3.86, the first command of a venue started on the production contract; venue operations announce the date on any environment where that line lies ahead. |
STP_CANCEL | no | Self-trade prevention cancelled it under your configured policy. Can hit the resting order, the taker, or both, depending on policy. |
REDUCE_ONLY_VIOLATION | no | A WORKING reduce-only order was trimmed or cancelled to respect the reducible budget after your position moved. |
SPOT_UNFUNDED | no | A resting spot order lost its funding and was cancelled at fill time. |
LIQUIDATION_CANCEL | yes | The venue pulled your working orders when a liquidation episode opened on your account. |
ADMIN_CANCEL | yes | Venue operations cancelled it. |
INSTRUMENT_HALT | yes | Cancelled by an instrument halt or delisting flow. |
MEMBER_SUSPENDED | yes | Cancelled because the member was suspended. |
POSITION_SIZE_LIMIT | yes | Settling the fill would have taken the position beyond the venue's maximum representable size, so the order was cancelled instead. Not reachable at any tradable size. |
MARKET_ACCESS | yes | The venue cancelled the order on a DENIED market access decision for your account on that market, or on the market's switch to admission-only access while you hold no access decision (revision 3.63; the value is named since 3.65, it read NONE before). Reduce-only flow stays admitted and open contracts run to term. |
FUNDING_CATCH_UP | yes | The venue cancelled a conditional order whose trigger fired while the market's GRID funding catch-up was pending, the window in which the market's funding settlement is more than 60 s overdue and opening flow answers 3014 (Member API revision 3.70, section 7); reduce-only conditionals execute. Dormant on the sandbox until the operator decision batch epoch announced by venue operations. |
POST_ONLY_WOULD_CROSS | yes | On a scheduled SECURITY market (member reference section 6j, revision 3.70): a POST_ONLY order that rested through a call and was still crossing when the call ended without an uncross print for it is cancelled when the market enters CONTINUOUS, because re-entering it would cross the book (a new order in the same case answers 2005). The same value on the REST order read, the WebSocket order frame and FIX Text(58). |
Executions
| Field | Values | Notes |
|---|---|---|
role | MAKER, TAKER | Drives which fee rate applied. |
flags | NORMAL, LIQUIDATION, ADL, RFQ, BARRIER_CLOSE, SWAP, REVERSAL, AUCTION | Single value, not a set. AUCTION (revision 3.67) marks a print of a call auction uncross on a SECURITY market, both sides at the maker rate; it ranks below RFQ and above NORMAL. SWAP (revision 3.51) marks a yield contract take (a take on a SWAP market): price is the offer's rateBps, qty the taken asset quantity, the taker's side SELL, fees in the asset; it prints on the public trades feed too but is not price data. REVERSAL wins over everything (the venue unwinding an erroneous print at the original price between the same two members, reference KX-RV-{originalTradeId}, fee-free, no last-trade print, no conditional triggering); ADL wins over LIQUIDATION when both would apply. RFQ marks a trade executed through the RFQ flow: it appears in your executions and on the public trades feed but never updates the last-trade print, never fires conditional orders and never feeds the circuit breaker. |
refSource | MEMBER, VENUE | MEMBER means clOrdId is your own reference. VENUE means it is venue-generated: KX-LQ-{orderId} on a liquidation slice, KX-ADL-{tradeId} on an ADL close. |
refSource, never on the shape of clOrdId. String-matching the KX- prefix yourself works today and will keep working, but it duplicates a venue invariant in your code. refSource is the contract; the prefix is the implementation. A fill with refSource: "VENUE" has no order you submitted and no clOrdId in your order book: if your reconciliation looks the reference up and throws on a miss, a single liquidation slice takes your session down.venueEvents channel
type, treated as an OPEN set: SNAPSHOT, STOP_TRADING, RESUME_TRADING, LIQUIDATION, ADL, MARGIN_CALL, LIQUIDATION_COMPLETE, on the private socket MEMBER_PARAMS (revision 3.61: a change of your own parameters, the names only; re-read GET /v1/account/parameters) and, since revision 3.70, the private advisory MARGIN_WATCH ({state ABOVE | CLEARED, ccy, utilisationPct, thresholdPct, reArmPct}, your margin utilisation crossing or clearing the venue's early-warning level; informational, not a margin call), plus the advisory types STALE_MARK_WARNING and STALE_MARK_RECOVERED (mark-price staleness ladder, both sockets, eventId namespace advisory-{asOfSeq}-{n}, never in the SNAPSHOT and never backfilled; full detail on the WebSocket page). A type you do not recognise is a new venue event, not corruption: log and continue.
| Event | Field | Values |
|---|---|---|
STOP_TRADING | reason | CIRCUIT_BREAKER, STALE_MARK_PRICE, DELISTING, ADMINISTRATIVE |
RESUME_TRADING | reason | ADMINISTRATIVE, CIRCUIT_BREAKER_EXPIRY, MARK_PRICE_RECOVERED |
STOP_TRADING / RESUME_TRADING / SNAPSHOT | tradingState | TRADING, HALTED, REDUCE_ONLY, POST_ONLY, DELISTED |
MARGIN_CALL | state | ACTIVE (you are on call), CLEARED (the condition ended). The same event also carries riskStatus, equity, initialMargin, maintenanceMargin, available and marginRatio, computed identically to GET /v1/account so the two can never contradict each other for the same instant. |
LIQUIDATION_COMPLETE | trigger | MARGIN_BREACH, ADMIN_FORCED |
LIQUIDATION_COMPLETE | memberStatus | ACTIVE, POST_ONLY, REDUCE_ONLY, SUSPENDED |
Only a CIRCUIT_BREAKER stop carries an expectedResumeTs. Every engine-derived venueEvents frame carries eventId formed as {asOfSeq}-{eventIndex}, which is globally unique and stable across reconnects: use it as your idempotency key. The one exception is the subscribe-time SNAPSHOT, whose eventId is snapshot-{asOfSeq}. That is deliberately NOT an idempotency key, because two subscribes at the same sequence number are two genuinely distinct snapshots and produce the same string. Apply a snapshot by replacing state, then dedupe the frames that follow it by eventId.
Trading state and instrument status spellings
Two surfaces publish the same facts with different spellings. The legacy spellings on GET /v1/status, GET /v1/instruments and GET /v1/account are frozen for already-deployed clients and will not be changed.
| Fact | venueEvents.tradingState | Instrument status on REST |
|---|---|---|
| Trading normally | TRADING | ACTIVE |
| Halted | HALTED | HALTED |
| Reduce-only session | REDUCE_ONLY | REDUCEONLY |
| Post-only session | POST_ONLY | POSTONLYSESSION |
| Delisted | DELISTED | DELISTED |
| Fact | venueEvents.riskStatus | GET /v1/account riskStatus |
|---|---|---|
| Healthy | NORMAL | NORMAL |
| On margin call | MARGIN_CALL | MARGINCALL |
| In liquidation | LIQUIDATION | LIQUIDATION |
| In default management | DEFAULTED | DEFAULTED |
venueEvents, map both onto your own enum at the edge. A risk kill-switch that matches on "MARGIN_CALL" will silently never fire against the REST spelling MARGINCALL.Delivery and recovery
| Field | Values | Meaning |
|---|---|---|
Gap frame reason | SLOW_CONSUMER | TERMINAL. Close 4008 follows immediately. Guaranteed events were withheld and are recoverable by cursor. |
Gap frame reason | CONFLATED | NON-TERMINAL. Best-effort market data was conflated under pressure; the socket stays open. Recovery is a state re-read, never a cursor, so no event index rides on this frame. |
Gap frame delivery | GUARANTEED, BEST_EFFORT | The delivery class of what was lost. GUARANTEED families are never silently dropped; BEST_EFFORT families are droppable by design. |
Backfill store.status | COMPLETE | The durable store is level with the engine. Paging to the end really is the end. |
Backfill store.status | CATCHING_UP | The store is authoritative only through contiguousThroughSeq. Poll again with the returned cursor. |
Backfill store.status | REPAIRING | The store of the node that answered has a recorded hole. Reads are clamped below it, so you stall rather than skip. Keep polling AND raise an operational alert. It starts at every leader change of a clustered venue, no timeout ends it and the venue sets no maximum duration; it ends when the hole is re-folded from the venue's journal, and while it stands lagSeq counts from the start of the hole to the engine head (member reference section 11.3, Member API revision 3.83). |
Guaranteed WebSocket families: order, execution, wallet, unwind, adl, eligibility, margin, venueEvents, rfq, barriers. Of those, order, execution, wallet, unwind, adl, eligibility and margin have a durable row source and can appear in a gap frame's backfill.query. The other three recover by state, not by cursor, and therefore show up under unrecoverable: venueEvents is re-seeded from its subscribe-time SNAPSHOT, and after a gap on rfq or barriers you re-read GET /v1/rfq/mine, GET /v1/rfq/open and GET /v1/barriers. Every other channel is best-effort.
GET /v1/stream/events accepts exactly these ten channels values: order, execution, wallet, margin, adl, unwind, eligibility, funding, status, swaps (revision 3.51, the yield contract lifecycle). Anything else is 2019.
Account and reference data
| Field | Values | Notes |
|---|---|---|
leverageMode | FIXED, TIERED | FIXED means one open-ended risk tier and maxLeverage is present and exact. TIERED means maxLeverage is null, because effective leverage is size-dependent and no single number is true for every order. |
kind | PERP, SPOT, BARRIER, SWAP, SECURITY | Determines whether positions, funding and reduceOnly exist at all. SECURITY (revision 3.66) is a DLT financial instrument under Regulation (EU) 2022/858: it behaves exactly like SPOT (prefunded delivery, no positions, no funding), its baseAsset is the ISIN, and it settles on the Kronos X chain (REST page, DLT Market). Since revision 3.67 a SECURITY market may carry an auction schedule and publishes tradingPhase (REST page, Call auctions). BARRIER markets carry knock-out contracts, have no funding, and always trade by RFQ. SWAP markets (revision 3.51) carry writer-first fixed yield contracts in the market's baseAsset, have no price, no mark and no margin, and always trade by RFQ with the verbs inverted (the writer posts an offer, any other member takes a size, nobody quotes). |
mode | ORDER_BOOK, RFQ | How the market trades. ORDER_BOOK is the central limit order book. RFQ markets run no book at all: order entry there is 2022, and RFQ requests on an ORDER_BOOK market are 2023. Read the mode from GET /v1/instruments and branch your routing on it. |
funding history skipReason | NO_POSITION_SNAPSHOT, NO_MARK_SNAPSHOTS | Revision 3.70. On the rows GET /v1/funding/history?includeSkips=true adds (skipped true) and on the WebSocket funding.{symbol} type: "skipped" frame: a LOCAL_DAILY day that closed without a settlement, no funding exchanged and none charged later. An OPEN set: treat an unknown value as a new reason. The funding.{symbol} frame type set is rate, settlement, settlementDetail (LOCAL_DAILY only) and skipped (LOCAL_DAILY only). |
wallet entry kind | DEPOSIT, WITHDRAWAL, TRADEPNL, FEE, FUNDINGPAY, FUNDINGRECEIVE, LIQUIDATIONLOSS, INSURANCEFUNDFLOW, ADLSETTLEMENT, MANUALADJUST, SPOTTRADE, FINALSETTLEMENT, BARRIERSETTLE, RECOVERYDISTRIBUTION | Ledger entry kinds, on the wallet channel and in ledger history. Upper-cased with no separator characters: the spelling is BARRIERSETTLE, not BARRIER_SETTLE. BARRIERSETTLE is the realised P&L from the close of a barrier contract, on knock-out, take profit, expiry, liquidation tear-up, final settlement or an early close by RFQ; it is zero-sum between holder and writer, and no fee rides on it (an early close's fees are ordinary FEE entries against its BARRIER_CLOSE trade). Revision 3.51 adds SWAPPRINCIPAL (a yield contract take: the taker's principal moving to the writer, ccy = the market's asset, reference = the contract id) and SWAPSETTLE (principal plus reward, or the part the writer could pay, writer to taker at maturity, at a later cure of a claim, or at the final settlement of a delisted swap market; same reference); the take's fees are FEE entries in the asset against its SWAP trade. Revision 3.30 added RECOVERYDISTRIBUTION (your pro rata share of an amount recovered from a defaulted member whose auto-deleveraging closed your position, reference = the episode id). |
swap status | OPEN, DEFAULTED, MATURED, CURED, FINAL_SETTLEMENT | Revision 3.51. GET /v1/swaps rows and the WebSocket swaps frames (types opened, matured, default, cured, finalSettlement). OPEN and DEFAULTED are live (a DEFAULTED contract carries an outstanding claim the venue keeps retrying); the other three are closed. Swap role: WRITER, TAKER. |
trade flags | NORMAL, LIQUIDATION, ADL, RFQ, BARRIER_CLOSE, REVERSAL, SWAP, AUCTION | One value per trade by priority, on GET /v1/trades, GET /v1/executions, the WebSocket trades and execution frames and the FIX trade tape (35=X, venue tag 9014). BARRIER_CLOSE (revision 3.44) is the closing trade of an accepted barrier close request, ranking above RFQ; SWAP (revision 3.51) is a yield contract take, also ranking above RFQ; AUCTION (revision 3.67) is a call auction print on a SECURITY market, ranking below RFQ and above NORMAL. |
barrier strictTakeProfit | true, false | Revision 3.45. A STRICT take-profit contract pays only if its take profit is touched before its expiry and forfeits the whole premium at expiry otherwise; requested with barrier.strictTakeProfit, echoed on every RFQ view, the accept response, GET /v1/barriers rows and every barriers frame. |
barrier premiumBps, instrument knockOutRebateBps / strictTakeProfitPremiumBps | integers, basis points of the barrier distance | Revision 3.45. The holder's premium (its maximum loss) is |entry - barrier| x qty x contract value x premiumBps / 10000, frozen on the contract at the open: 10000 - knockOutRebateBps on a normal contract (1000 = a 10 percent knock-out rebate on every barrier market listed today), strictTakeProfitPremiumBps on a strict one (5000 today; 0 = strict take profit not offered), 10000 on contracts opened before the rebate existed. Published as premium (money string) and premiumBps on every barrier surface, and as the two instrument fields on GET /v1/instruments (BARRIER markets only). |
barrier terminationReason | KNOCK_OUT, TAKE_PROFIT, EXPIRY, TEAR_UP, FINAL_SETTLEMENT, EARLY_CLOSE | Closed rows of GET /v1/barriers?status=CLOSED and the WebSocket barriers close frames (types knockOut, takeProfit, expiry, liquidation, finalSettlement, earlyClose). EARLY_CLOSE (revision 3.44) is the holder's close request accepted at the writer's quoted price. |
adlQuintile | integer 1 to 5 | Not a string. 5 means first in line to be auto-deleveraged. Recomputed every 5 seconds (the ranking basis is stated on the REST page beside GET /v1/adl/indicator); treat it as a live risk signal, not a static attribute. |
Retry matrix
| Codes | Retry | How |
|---|---|---|
| 1002, 1003, 1004, 5001, 5007 | yes | Backoff with jitter. Reuse the SAME clOrdId on order entry: nothing was accepted, and an identical payload is idempotent anyway. Every retry is signed again with a fresh timestamp, never sent as the same bytes: since Member API revision 3.85 a signed write is accepted once, and identical bytes answer 1002 request already received; a 1002 naming X-RECV-WINDOW or a timestamp ahead is fixed first. A 5001 batch row: resend that entry alone, its neighbours may have taken effect. A single write answered 5001 during a leader change on a clustered deployment may still execute: reconcile it by clOrdId on the leader before resending it, or resend it with the SAME clOrdId (Member API revision 3.83). A 1003 whose msg is rate limit: repeated engine faults (5005) (Member API section 4): sleep retryAfterMs, up to 60 seconds, and never resend the commands that answered 5005 unchanged. Cancellations and the requests that can only reduce your risk (Member API section 4) are not refused by that budget. |
| 3010 | no | Not for that identifier while the stop stands (its unmarked flow, and every other identifier, trade on); appeal under Rules section 36. |
| 3011 | yes | After 00:00 UTC, when the day's DEA-flow allowance resets. |
| 3012 | yes | When GET /chain/v1/head shows unattestedTail: false; the same request, nothing was debited. |
| 5006 | no | A venue-side fault on the chain host; retry after a minute, then contact venue operations. |
| 4001 | yes | Only after re-reading the current version and reapplying your change on top of it. |
| 5030 | yes | As a redirect, never as a rejection: when leader equals the entry host you configured, send the request again there on a new connection with the same clOrdId (a cancel or an amendment at once); otherwise reconnect to your entry host and tell venue operations. Nothing was journaled on the node that answered. No backoff is needed and none helps, because the same node will refuse the same write until it is the leader. |
| 5000 | conditional | Reads: retry freely. Order entry: RECONCILE by clOrdId on the leader first, because the command may have been journaled before the error, a WebSocket write ack 5000 included (Member API revision 3.83). Blind resubmission risks a double position. A batch row (Member API revision 3.74): read the order back on the leader first. |
| 5005 | no | Never unchanged: a deterministic venue fails an identical request identically. The response reports what the command did achieve before it stopped, so reconcile against that and GET /v1/orders/open first, then send a different request if you still need the position. A batch row carrying 5005 is reconciled before any resend and never resent as it was. |
| 3003, 3005, 5002 | no | Not a retry matter. Wait for the state change: RESUME_TRADING for 3003, a live mark for 3005, venue resume for 5002. |
| 3007 | no | Not retryable unchanged, and nothing on your side needs fixing: the quoting member failed its own margin or limit check. Accept a different quote or wait for fresh ones on the same RFQ, which stays open for the rest of its window. |
| 1001, 1005, 2001 to 2046, 3001, 3002, 3004, 3006, 3008, 5003, 5004 | no | Fix the cause. 2046 clears with the phase: re-send as a GTC or GTD LIMIT for the uncross, or wait for tradingPhase CONTINUOUS. Retrying an unchanged request produces the same result and consumes your rate limit. The exception is the 2009 identical-payload path, which is not a retry of a rejection but the venue's deliberate idempotency mechanism. 2045 means a different live clOrdId of yours collides on its hash: re-send with a different clOrdId. 2031 clears once the identity record is PUT, not by resending the GET. 2032 means the lot is gone or was never open: re-read GET /v1/lots and act on what it returns. 2036 to 2038 are swap term and size errors: re-read GET /v1/instruments and GET /v1/rfq/open. 2039, 2040 and 2043 clear only with fresh quotes or a moved mark, 2041 and 2042 only with wider levels on a new request. 3008 clears only when your last outstanding swap claim is paid: fund the asset and watch GET /v1/swaps. |
Glossary
- mark price
- The venue's sequenced fair price for a perpetual, used for margin, unrealized PnL, price bands and
MARKtriggers. It is not the last trade price. An instrument with no sequenced mark yet rejects orders with 3005. - index price
- The external reference price the mark is anchored to. Available at
GET /v1/index-priceand usable as a conditional trigger source. - bankruptcy price
- The price at which a defaulting position's remaining margin is exactly exhausted. ADL closes execute at the bankruptcy price, which is why an ADL fill can print away from the visible book.
- ADL
- Auto-deleveraging. The last resort of the liquidation waterfall: when the book cannot absorb a default, opposite-side counterparties are closed at the bankruptcy price, profitable positions first, highest score first, and the remaining opposite-side positions, including positions not in profit, only where the profitable ones cannot absorb the whole position. You cannot opt out, and it produces fills with
flags: "ADL"andrefSource: "VENUE". - unwind episode
- One liquidation or forced-close event on one member, with its own
episodeId. It groups the order cancellations, book-facing liquidation slices and any ADL allocations that belong together, and closes with aLIQUIDATION_COMPLETEevent. Query yours atGET /v1/unwinds. - Market Segments
- The venue's counterparty-acceptance mechanism: you may restrict which members you are willing to trade with. Matching requires MUTUAL acceptance and the default is open-to-all. Restricted liquidity is still visible in the full book, so use the matchable book to see what you can actually hit. This is not the ESMA segment-MIC concept.
- clientUid
- Your opaque end-client reporting identifier, sent on every order (2010 if missing). It scopes end-client STP and drives regulatory reporting. It is not a venue account: margin and positions are member-level.
- clOrdId
- Your own order reference, unique per member for 24 hours. It rides every response and event for that order's lifecycle. Reusing it with a different payload is 2009; reusing it with an identical payload is the idempotent retry path. The venue holds the reference for 24 hours or until it releases it to stay inside its retention bound of the most recent 4,000,000 references venue-wide, whichever comes first, and never releases one whose order is still open. The
KX-prefix is reserved (2020). - asOfSeq
- The engine sequence number a response or frame is true as of. It is the single total order across the whole venue. Use it to decide whether a snapshot is older or newer than an event you already applied, and to discard duplicates after a backfill.
- cursor
- An opaque 30-character numeric token encoding a position in the durable member event stream, finer-grained than
asOfSeqbecause it also pins event index and leg. Fetch one fromGET /v1/stream/cursor, page with it, never construct one. - drop copy
- A READ_ONLY credential scope. It receives the member's full execution flow but is refused on every write, on every transport, with 1005. Give it to back office, risk and surveillance so those systems provably cannot trade.
- OTR regime
- The RTS 9 order-to-trade ratio control. Order messages (new, amend AND cancel) are counted over a sliding 60 minute window; the regime only binds after a floor of 100 messages, so a quiet session is never blocked. Breaching the block threshold returns 1004 on new orders and amendments; cancels are always admitted.
- insurance fund
- A toggleable engine capability that is disabled on this MTF, so no shortfall buffer exists:
GET /v1/insurance-fundreturnsenabled: falsewith zero balances, and a defaulter's residual after ADL books to the venue rather than to a mutualized fund.insuranceFundDeltaonLIQUIDATION_COMPLETEis therefore always"0".