# Kronos X Member API, Reference (v1)

Trading API for venue members. The venue operates five market kinds side by side:
**perpetual swap/future markets** (`"kind": "PERP"`, cash-settled, margined,
funded), **spot markets** (`"kind": "SPOT"`, fully prefunded delivery of the
base asset against the settlement currency, offered under the venue's MiCA
notification), **barrier markets** (`"kind": "BARRIER"`, bilateral knock-out
contracts, traded by request for quote), **swap markets** (`"kind": "SWAP"`,
writer-first fixed yield contracts in one asset, offers taken by request for
quote, section 6h) and **DLT financial instruments** (`"kind": "SECURITY"`,
tokenised securities settling on the Kronos X chain, prefunded like spot,
sections 6i and 6j). One API, one authentication scheme, one order model;
per-kind differences are called out inline and summarized in §6a, §6b, §6h and §6i.

**Document status:** Sandbox release for member integration testing. Document
revision 3.86, 21 October 2026. Path and field naming may receive a final
compatibility alignment before production go-live; any change will be communicated
with a migration table.

**Revision 2.5 ships with a venue data reset.** The quantity model change and the
market re-listing described in section 2a take effect together with a full reset
of venue data (orders, positions, balances and history) on a date announced by
venue operations. Sandbox first, then production before go-live. Re-read
`GET /v1/instruments` after the reset; do not carry cached instrument parameters
across it.

**Versioning and deprecation policy.**

- The API major version is in the path (`/v1`). Within `/v1`, the venue makes
  ADDITIVE changes without notice: new endpoints, new optional request fields, new
  response fields, new WS channels, new event types on existing channels, and new
  enum values where the documentation marks the set open. Build parsers that
  ignore unknown response fields and unknown event types; do not build strict
  parsers of venue responses.
- BREAKING changes (removing or renaming a field or endpoint, changing a field's
  type or semantics, narrowing accepted input) ship only as a new major version
  path or, before production go-live, with at least 30 days written notice to
  every member and a dated migration table appended to this document.
  Compatibility aliases (for example `/v1/eligibility*` for
  `/v1/market-segments*`) stay in place for the life of `/v1`.
- Technical bounds on authenticated reads (the in-flight bound, the shared
  budget, the time bound, the hold and the store yield of section 4) may be
  introduced or tightened with a changelog entry and without the notice of a
  breaking change; they never change a field, a route or the meaning of a code.
- Error-code numbers are never reused or repurposed. New codes are additive.
- After a `/v2` ships, `/v1` remains served for at least 6 months, with the
  retirement date announced when `/v2` goes live.
- Every revision of this document carries a revision number and date above; the
  changelog is section 13.

---

## 1. Environments

| Environment | REST base URL | WebSocket base URL |
|---|---|---|
| **Sandbox (test)** | `https://sandbox.mtf.perpetuals.net` | `wss://sandbox.mtf.perpetuals.net` |
| **Production** | `https://production.mtf.perpetuals.com` | `wss://production.mtf.perpetuals.com` |

- All REST paths below are relative to the base URL (e.g. `https://sandbox.mtf.perpetuals.net/v1/instruments`).
- **The sandbox host (revision 3.83).** The sandbox is `sandbox.mtf.perpetuals.net`,
  a clustered deployment of three nodes behind that one host (section 1a). It
  replaces the single-node sandbox at `sandbox.mtf.perpetuals.com` that revision
  3.24 published, from the date venue operations announce: point your connector
  at the new host from that date. Each environment publishes ONE host, the entry
  host above, which sends new connections to the node that accepts writes; node
  addresses are not published and are not member endpoints, so never pin a node
  (section 10.7).
- WebSocket endpoints: `/ws/public` (market data) and `/ws/private` (account data + order entry).
- Sandbox API keys are issued by venue operations at onboarding; after that,
  members issue and revoke their own keys from the member portal keys card
  (section 3). Sandbox balances are test money. Sandbox marks come from live
  market reference prices where a reference feed is mapped, with a price
  simulator (gentle random walk) covering the rest, so marks and triggers move
  like production; the funding RATE, by contrast, is fixed (section 1a lists
  the deliberate sandbox differences). The sandbox operations policy (reset
  notice, baselines, top-ups, parity with production) is section 1a.
- **Build and certify against sandbox.** Production endpoints are enabled per member
  at onboarding, after conformance testing, and production credentials are issued
  separately. Use the sandbox host until venue operations confirm production access.
- **TLS is mandatory.** Both environments serve HTTPS/WSS only, with a publicly
  trusted certificate (Let's Encrypt); plain HTTP is not exposed. Standard system
  trust stores validate the chain, no certificate pinning is required.

**Interactive reference.** Every REST endpoint is also browsable and executable at
`/docs` on either environment (for example
`https://sandbox.mtf.perpetuals.net/docs`). Paste your sandbox key and secret into
the bar at the top and the page signs each request in your browser, so "Try it out"
works end to end without writing any signing code first. The machine-readable
OpenAPI 3.1 description is at `/openapi.json`.

## 1a. Sandbox operations and production parity

The sandbox runs under a set of operator-ratified rules that the venue enforces
internally; they are restated here so integration teams can plan against them.
Operational communications go out via the members Slack workspace, the member
portal, and revisions of this document. The sandbox is available during market
hours on a best-efforts basis and observes the same maintenance window as
production (Saturday 02:00 to 06:00 UTC, section 5); no service credit applies
to it.

**Data resets.**

- A FULL sandbox data reset (orders, positions, balances and history) is
  announced at least 5 business days in advance, via the members Slack channel
  and a notice in the member portal. Where possible, resets are scheduled
  together with breaking manual revisions, so one migration covers both.
- A reset invalidates golden captures and standing test state. The announcement
  names the post-reset baseline: the manual revision in force, the instrument
  set, and the standard test balances members restart with.
- API credentials survive a data reset unless the announcement says otherwise,
  and portal logins persist. Re-read `GET /v1/instruments` after a reset and do
  not carry cached instrument parameters across it.
- Resting orders and other standing test state survive ordinary releases; only
  an announced reset clears them.

**Test-member state on request.**

- Margin top-ups for test members are available on request via the members
  Slack channel and are normally booked the same business day.
- Per-member state resets (flatten positions, restore the standard test
  balances) are available on request and are executed in the next maintenance
  window.

**The clustered sandbox (revision 3.83).** The sandbox at
`sandbox.mtf.perpetuals.net` runs three nodes behind its one entry host: one
node, the leader, accepts writes, and the entry host sends new connections only
to it. A leader change therefore happens on the sandbox as it can on
production (a rollout, a node fault), with everything section 10.7 describes
for it: `5030` from a node that is not the leader, the WebSocket close `1001`
`leader change, reconnect`, the FIX `Logout(35=5)` with the same text, and a
`REPAIRING` window of the event stream store after it (section 11.3). For Conformance Test Plan case I-05, venue
operations stage a leader hand-over at an agreed time, as they stage the margin
call and liquidation cases R-06 and R-07.

**Sandbox to production parity.** Sandbox instrument risk tiers match
production on every listed perpetual: a three-tier ladder with initial margin
2 percent up to 1 million USD notional, 5 percent to 5 million, and 10 percent
above, maintenance margin half of each tier's rate. The production listing as
planned today also uses the sandbox's grid (`tickSize`, `qtyStep`, `minQty`)
and `minNotional` on every market both environments list (section 2a); the
values production publishes on `GET /v1/instruments` from its first day are the
binding ones. Production lists the perpetual and the knock-out (`*-USD-KO`)
markets, and its instrument list differs from the sandbox's (section 2a):
the venue confirms it with the go-live listing. As always, read the live
values from `GET /v1/instruments`, per environment. Two deliberate differences
remain:

- The sandbox funding RATE on every GRID-scheduled perpetual (the crypto
  markets) is +0.0001 (0.01%) per 8 hour interval, published from the date
  venue operations announce as a FIXED rate, `funding.rateSource` `FIXED` with
  `funding.fixedRate` `0.0001` on `GET /v1/instruments` (until that date the
  rows read `rateSource` `TICK` and the rate stamped on every mark tick is the
  same +0.0001; a sandbox booted fresh after that date reads `FIXED` from its
  first listing): it never varies or changes sign on its own, so carry
  strategies validated here are one-sided in a way production, which computes
  a real rate, will not be. Members who need rate variation or a sign change
  for conformance testing can request a temporary change of the published
  fixed rate via the members Slack channel, like a margin top-up: venue
  operations set the requested rate on the market, inside its `rateFloor` to
  `rateCap`, as a published change of `funding.fixedRate` (its first
  publication on a market still reading `TICK`) that is in force for every
  settlement until it is reverted, and restore +0.0001 afterwards. The
  LOCAL_DAILY perpetuals (equity indices, gold, silver, oil, FX; section 7) are the
  exception: their rate is computed by the venue from the day's marks and
  index on the sandbox exactly as in production, so it varies and changes sign
  with the market.
- The stale-mark timeout is 60 seconds on the sandbox, against 300 seconds in
  production, so members can exercise the full staleness ladder (section 10.9)
  in minutes rather than waiting out a production-length window.

Production ISINs are pending DSB issuance; the sandbox `isin` values are test
identifiers, so treat instrument reference data as per environment. On barrier
markets the ISIN is allocated per CONTRACT term set and published on the contract
(`isin` on the accept result and on `GET /v1/barriers`, revision 3.70); the market
symbol carries none on production, and the sandbox, which files at market
granularity, publishes null on the contract rows.

## 2. Conventions

**Response envelope.** Every REST response uses:

```json
{ "code": 0, "msg": "ok", "data": { }, "asOfSeq": 123456789, "ts": 1700000000000 }
```

- `code`, `0` on success, a registry code (§9) otherwise. The HTTP status mirrors the class of error.
- `asOfSeq`, the engine sequence number the response was read at. Strictly monotone
  venue-wide; use it to order data across REST and WebSocket sources.
- `ts`, server time, Unix milliseconds.

**Numbers are strings.** All prices, quantities, rates, and money amounts are exact
decimal strings (`"65000.5"`, `"0.0002"`), never floating-point numbers. Send them as
strings too.

**Grid alignment.** Prices must align to the instrument's `tickSize`; quantities
must be exact multiples of the instrument's `qtyStep` (see `GET /v1/instruments`
and section 2a). Misaligned values are rejected with code `2002`. Since revision
3.60 the code tells shape from grid: 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) is the shape error `2013`; a numeral the grid
cannot admit (zero, negative, finer than or off the grid, out of range) and a
missing required price stay `2002` (section 9). The rule is the same on every
route and transport.

**Time.** All timestamps are Unix **milliseconds** unless a field says otherwise.

**Identifiers.**
- `orderId`, venue-assigned, unique forever.
- `clOrdId`, your order identifier, ≤ 36 chars, **unique per member per 24 h**. It is
  an idempotency key: resubmitting the *identical* order returns the original order
  with `"duplicate": true` and `code 0` (safe retry); reusing a `clOrdId` with a
  *different* payload is rejected with code `2009` (envelope-level, HTTP 400, the
  reference echoed in `data.clOrdId`). Strongly recommended on every order but not
  enforced: an omitted or empty `clOrdId` is accepted, the order simply has no
  duplicate protection, so a blind retry can double it. Before the matching
  engine's replay-contract line of revision 3.85 it is NOT an idempotency key on
  RFQ creation: `POST /v1/rfq` echoes its `clOrdId` and never deduplicates on it
  (section 6b, revision 3.83 states it); from that line it is (below).
  **How long the venue remembers a reference.** Uniqueness for 24 h is your
  obligation and it has not changed. What the venue keeps is the most recent
  4,000,000 references venue-wide, or 24 h, whichever is reached first, so on a day
  of exceptional venue-wide volume the effective duplicate-detection horizon is
  shorter than 24 h. A reference the venue has released behaves exactly like one that
  aged out: resending it places a NEW order rather than returning the original, and
  reusing it with a different payload no longer rejects `2009`. This never affects a
  reference whose order is still open: as long as an order of yours is working or
  pending trigger, its `clOrdId` is retained and stays usable for cancel and replace
  by reference. Retries are seconds-scale operations and are unaffected; if you
  reconcile hours later, reconcile by `orderId` or by `GET /v1/orders`, not by
  resending.
  **RFQ requests and quotes share the reference (revision 3.85).** An RFQ request
  (`POST /v1/rfq`: a request, a barrier close request, a yield contract offer) and a
  quote (`POST /v1/rfq/{rfqId}/quotes`) are idempotent on their `clOrdId` like an
  order: a reference you already used, on an order, a swap take, a request or a
  quote, answers `2009` and nothing is opened or replaced (section 6b; from the matching
  engine's replay-contract line for this release, before which a reused reference is
  admitted as before). One namespace
  per member, retained exactly as above. Unlike an order there is no restatement of
  the original: read `GET /v1/rfq/mine` or `GET /v1/rfq/quotes/mine` to find what
  your first send opened.
- `clientUid`, **required on every order**: your opaque identifier for the ultimate
  end client (≤ 64 chars, `[A-Za-z0-9._:-]`). Market makers may omit it, in which
  case the configured house identifier is applied. The venue tracks per-`clientUid`
  sub-positions for your reconciliation; they carry no margin consequences, margin
  is member-level only. With hedge mode (the optional `positionSide` order field,
  section 6) one `clientUid` can hold a long and a short sub-position in the same
  instrument at once. Orders never carry client identities; where the venue
  reports your transactions under MiFIR, the identification data behind a
  `clientUid` is submitted separately, decoupled from trading (section 6d).

  > **`clientUid` values are permanent and the table they live in is shared.**
  > The venue's end-client identifier table is venue-wide, capped at 1,048,576
  > distinct identifiers, and NEVER pruned, because each entry carries that end
  > client's lifetime realized PnL and funding. A member that mints a fresh
  > `clientUid` per session or per order consumes shared capacity permanently,
  > on every member's behalf; when the table fills, the admitting order rejects
  > `2029` and nothing can recover the space. `clientUid` is the ultimate
  > client's STABLE reference (that is `clOrdId`'s job per message): send the
  > identifiers you actually use, one per real end client, for the life of the
  > relationship.

- **No surrounding whitespace in a reference (revision 3.72).** A `clOrdId`, a `clientUid`
  and the three Trader IDs of the `reg` block (`traderId`, `investmentDecisionId`,
  `executionDecisionId`) must not BEGIN or END with a whitespace character (a blank, a
  tab, a line break, a no-break space). No documented alphabet ever contained one. From
  the date venue operations announce for revision 3.72 the venue REFUSES such a value
  with `2013` and a sentence naming the rule, before anything is sequenced: on
  `POST /v1/orders` and on every entry of `POST /v1/orders/batch` (the entry's own
  `code` and `msg`), on the RFQ routes, on the WebSocket (the `order.place` ack carries
  the code and the sentence), on FIX (ONE ExecutionReport, `150=8`, `39=8`, `9010=2013`,
  for ClOrdID(11), Account(1) and the tags 9012, 9024 and 9025, and with its own
  sentence for TargetOpenClOrdID(9028)) and on the identity
  routes of section 6d, which take the identifier from the URL path (percent-encoded
  whitespace counts). The matching engine compares a reference character for character,
  so one that differs from another only by surrounding whitespace is a DIFFERENT
  reference to it, while the venue's history records must never take the two for one
  client or one order: the refusal keeps both views identical. A cancel that names an
  order by reference (`DELETE /v1/orders?clOrdId=`, OrigClOrdID(41)) is a lookup and is
  NOT refused: references are compared character for character as before, so an order
  you placed under a padded reference before the announced date stays cancellable under
  exactly that reference, and by `orderId` on every transport; any other padded
  reference answers `2014` (FIX: OrderCancelReject). Amends and the FIX replace address
  the order by its order id and carry no reference. A read filter (`clientUid=` on the
  lot reads of section 6e) is a lookup and is not refused; send it without padding,
  because the history reads ignore a trailing blank in a filter value. A member
  with registered Authorised Users reads `2021` for a padded `reg.traderId`, because
  that check runs first and no padded Trader ID can be registered.

  **Migration table, revision 3.72** (dated 21 September 2026; the versioning policy
  above treats a narrowed input as a change that is announced in writing):

  | What you send | Before the announced date | From the announced date | What to do |
  |---|---|---|---|
  | `clOrdId`, `clientUid`, `reg.traderId`, `reg.investmentDecisionId` or `reg.executionDecisionId` that begins or ends with whitespace, on an order, a batch entry or an RFQ request, quote, accept or take | accepted and recorded as sent | `2013` with the sentence; nothing is sequenced | trim the value where you build it; a value you already use WITH the padding names a different client or order than the trimmed one, tell venue operations before you change it |
  | the same in ClOrdID(11), Account(1) or tag 9012, 9024, 9025 of a NewOrderSingle | accepted | ExecutionReport `150=8`, `39=8`, `9010=2013`, the session stays up | as above |
  | a padded TargetOpenClOrdID(9028) on a NewOrderSingle | accepted; it identified no opening order unless that order's own reference carried the same padding, and the close followed FIFO | ExecutionReport `150=8`, `39=8`, `9010=2013` with its own sentence | trim it; target a lot you opened under a padded reference by its lot id, tag `9020` |
  | a padded reference on a cancel (`DELETE /v1/orders?clOrdId=`, OrigClOrdID(41)) | looked up character for character | unchanged, never refused: the order placed under exactly that reference is cancelled, any other padded reference answers `2014` | nothing; an order placed before the date stays cancellable by its exact reference and by `orderId` |
  | a padded `clientUid` in the path of `PUT` or `GET /v1/clients/{clientUid}/identity` | stored or read under the value as sent | `2013` | as above |
  | a padded `traderId` in the path of `PUT` or `GET /v1/trader-ids/{traderId}/identity` | `2030` on the PUT (the alphabet rule), `2031` on the GET | `2013` on both | as above |

## 2a. Contract sizing and granularity

Every quantity on this API, order `qty`, RFQ `qty`, position `netQty`, book
depth, executions, is a **decimal number of base-asset units**. On
`BTC-USD-PERP`, `"qty": "0.0014"` means 0.0014 BTC. Prices are quoted **per
base unit** (per BTC, per ETH), so the notional of an order is simply
`price × qty` in the settlement currency.

**Exception, the two JPY markets.** On JPY-USD-PERP and JPY-USD-KO one base
unit is 1,000 JPY, not 1 JPY: `qty` `"1"` is 1,000 JPY, prices are USD per
1,000 JPY (about 6.5 while USD/JPY trades near 153), and the tick of 0.0001 per
unit equals 0.0000001 USD per JPY. `baseAsset` reads `JPY` on both rows and the
instrument row carries no unit text, so this sentence and the Contract
Specifications for the USD-settled tradfi perpetuals are where the unit is
stated; every quantity on these markets (orders, RFQs, positions, depth,
executions, FIX tag 38) is a count of 1,000 JPY units. The 1,000 JPY unit
applies from the re-listing date venue operations announce for the venue you
trade on; until that date a venue still serving the earlier 1 JPY listing
publishes `tickSize` `0.000001` on the two rows, and the listing values in force
are always the ones on `GET /v1/instruments`.

**The USD/JPY quanto (revision 3.75, sandbox).** On USDJPY-USD-PERP and
USDJPY-USD-KO the price is the USD/JPY exchange rate as quoted in the market,
yen per dollar (about 157), and one unit pays 1 USD for each full 1.000 move of
that rate: a pip (0.01) is 0.01 USD per unit and the tick of 0.001 is 0.001 USD
per unit, so `price × qty` is the notional in USD and every figure of this
document holds unchanged. The profit and loss is NEVER converted at the live
rate: the contract moves with USD/JPY but pays a fixed number of dollars per
point (a quanto), which is not the exposure of a cash USD/JPY position. It is
not the inverse of JPY-USD-PERP, which stays listed beside it on 1,000 JPY
units. Both quanto rows trade on the `qtyStep` `0.01` of a unit (one contract
about 1.57 USD at a rate of 157); `baseAsset` reads `USDJPY` on USDJPY-USD-PERP
and USDJPY-USD-KO, and the Contract Specifications for the USD-settled tradfi
perpetuals state the terms.

Three per-market listing properties on `GET /v1/instruments` govern
granularity and minimum size, and a fourth, `contractMultiplier`, states the
contract value:

- `qtyStep`, the quantity grid: every `qty` must be an exact multiple of it.
  A `qty` that is not, at ANY decimal depth, is rejected with code `2002`; the
  venue never rounds a quantity for you. Integer quantity strings remain valid
  everywhere (`"1"` is 1 base unit, an exact multiple of every step the venue
  lists).
- `minQty`, the smallest accepted quantity. On every current listing
  `minQty` equals `qtyStep`.
- `minNotional`, the smallest accepted order value: an order or RFQ whose
  `price × qty` falls below it is rejected with code `2028`. It is 5 USD on
  every current listing except the barrier markets (thirteen at revision 3.71,
  twenty-two since revision 3.75), where it is 100
  USD since revision 3.71 (the fractional barrier listing: on the sandbox from
  the re-listing date venue operations announce, on production from the first
  day). Priced orders are valued at their limit price; market orders (and the
  market legs of
  conditionals) are valued at the current mark price. Amends re-check the FULL
  amended quantity at the effective price. RFQs are checked twice: at create,
  valued at the current mark, and again on every quote, valued at the quoted
  entry price. Two exemptions exist, both closing-only: venue-initiated closes
  (liquidation and ADL) bypass the floor, and your own `reduceOnly` orders and
  amends within your reducible position bypass it too. A position whose whole
  notional has fallen below the floor is therefore always closable: submit a
  `reduceOnly` order of exactly the position size (a `reduceOnly` order larger
  than your reducible position rejects with `2008`, so never oversize the
  close). Orders that could OPEN or grow exposure always face the floor.
- `contractMultiplier`, the contract value: the number of base units one
  contract relates to, a decimal string. It is `"1"` on every market listed
  today and on BARRIER markets it is FIXED at 1 by listing rule, a term of the
  Contract Specifications, Barrier Contracts (Downloads): one unit of quantity
  on a barrier market is one unit of the underlying (one index point, one troy
  ounce, one barrel, one unit of the base currency, 1,000 JPY on the JPY
  market, or one unit of the digital asset) priced in the settlement currency,
  the RFQ `qty` is a decimal number of units on the market's `qtyStep` (the
  step of the perpetual of the same underlying since revision 3.71; fractional
  sizing is the grid, never the multiplier), and every barrier figure this
  document writes as `distance x qty x contract value` is therefore
  `distance x qty`. Prices are
  per base unit and `qty` in base units on every kind, so `price x qty` is the
  notional whatever the field says.

**Worked example.** `BTC-USD-PERP`, tick `0.5`, `qtyStep` `0.0001`,
`minNotional` `5`: buy `0.0014` BTC at `74000` is a notional of
`74000 × 0.0014 = 103.60` USD, well above the 5 USD floor. The smallest
possible P&L increment on that quantity is one tick on 0.0014 BTC,
`0.5 × 0.0014 = 0.0007` USD. A `qty` of `"0.00145"` on the same market rejects
with `2002` (14.5 steps is not an exact multiple of `0.0001`), and so does
`"0.00005"` (below the step); a valid step that prices out below 5 USD, for
example `"0.0001"` at a 40000 mark, rejects with `2028` instead.

**Current listings** (informative; ALWAYS read the live values from
`GET /v1/instruments`, per environment, and re-read them after the announced
venue data reset). The table is the SANDBOX listing. Production lists the
perpetual and the knock-out markets, but not every row of this table: as
planned today, the thirteen perpetuals listed before the 30 September 2026
listing (BTC, ETH, SOL, BNB and XRP, and SP500, NDX100, XAU, WTI, BRENT, EUR,
GBP and JPY), the knock-out markets of BTC, ETH, SOL, BNB, XRP and JPY, and
the six swap markets; none of the eighteen rows of the 30 September 2026
listing (revision 3.75), none of the other seven knock-out rows and none of the
spot rows (their admission on production is a separate decision under the
venue's MiCA notification, as the Contract Specifications, USD Spot state).
The venue confirms the production instrument list with the go-live listing,
and on every market both environments list, the production configuration as
planned today carries the values of this table:

| Market | Price tick | `qtyStep` = `minQty` | `minNotional` |
|---|---|---|---|
| BTC-USD-PERP | 0.5 | 0.0001 | 5 |
| ETH-USD-PERP | 0.05 | 0.001 | 5 |
| SOL-USD-PERP | 0.01 | 0.01 | 5 |
| BNB-USD-PERP | 0.05 | 0.001 | 5 |
| XRP-USD-PERP | 0.0001 | 1 | 5 |
| HYPE-USD-PERP | 0.01 | 0.01 | 5 |
| DOGE-USD-PERP | 0.00001 | 1 | 5 |
| ZEC-USD-PERP | 0.05 | 0.001 | 5 |
| NEAR-USD-PERP | 0.0001 | 1 | 5 |
| TAO-USD-PERP | 0.01 | 0.01 | 5 |
| SP500-USD-PERP | 0.1 | 0.001 | 5 |
| NDX100-USD-PERP | 0.1 | 0.001 | 5 |
| XAU-USD-PERP | 0.1 | 0.001 | 5 |
| WTI-USD-PERP | 0.001 | 0.01 | 5 |
| BRENT-USD-PERP | 0.001 | 0.01 | 5 |
| EUR-USD-PERP | 0.0001 | 1 | 5 |
| GBP-USD-PERP | 0.0001 | 1 | 5 |
| JPY-USD-PERP | 0.0001 | 1 (= 1,000 JPY) | 5 |
| US500-USD-PERP | 0.1 | 0.001 | 5 |
| US100-USD-PERP | 0.1 | 0.001 | 5 |
| XAG-USD-PERP | 0.001 | 0.01 | 5 |
| USDJPY-USD-PERP | 0.001 | 0.01 (quanto, 1 USD per 1.000) | 5 |
| BTC-USD-KO | 0.5 | 0.0001 | 100 |
| ETH-USD-KO | 0.05 | 0.001 | 100 |
| SOL-USD-KO | 0.01 | 0.01 | 100 |
| BNB-USD-KO | 0.05 | 0.001 | 100 |
| XRP-USD-KO | 0.0001 | 1 | 100 |
| HYPE-USD-KO | 0.01 | 0.01 | 100 |
| DOGE-USD-KO | 0.00001 | 1 | 100 |
| ZEC-USD-KO | 0.05 | 0.001 | 100 |
| NEAR-USD-KO | 0.0001 | 1 | 100 |
| TAO-USD-KO | 0.01 | 0.01 | 100 |
| SP500-USD-KO | 0.1 | 0.001 | 100 |
| NDX100-USD-KO | 0.1 | 0.001 | 100 |
| XAU-USD-KO | 0.1 | 0.001 | 100 |
| WTI-USD-KO | 0.001 | 0.01 | 100 |
| BRENT-USD-KO | 0.001 | 0.01 | 100 |
| EUR-USD-KO | 0.0001 | 1 | 100 |
| GBP-USD-KO | 0.0001 | 1 | 100 |
| JPY-USD-KO | 0.0001 | 1 (= 1,000 JPY) | 100 |
| US500-USD-KO | 0.1 | 0.001 | 100 |
| US100-USD-KO | 0.1 | 0.001 | 100 |
| XAG-USD-KO | 0.001 | 0.01 | 100 |
| USDJPY-USD-KO | 0.001 | 0.01 (quanto, 1 USD per 1.000) | 100 |
| BTC-USD (SPOT) | 0.5 | 0.0001 | 5 |
| ETH-USD (SPOT) | 0.05 | 0.001 | 5 |
| SOL-USD (SPOT) | 0.01 | 0.01 | 5 |
| BNB-USD (SPOT) | 0.05 | 0.001 | 5 |
| XRP-USD (SPOT) | 0.0001 | 1 | 5 |
| USD-TBILL-SWAP | 1 (rate in bps, no price) | 0.01 | 100 USD |
| EUR-TBILL-SWAP | 1 (rate in bps, no price) | 0.01 | 100 EUR |
| BTC-STAKE-SWAP | 1 (rate in bps, no price) | 0.0001 | 0.001 BTC |
| ETH-STAKE-SWAP | 1 (rate in bps, no price) | 0.001 | 0.01 ETH |
| SOL-STAKE-SWAP | 1 (rate in bps, no price) | 0.01 | 1 SOL |
| USD1-STAKE-SWAP | 1 (rate in bps, no price) | 0.01 | 100 USD1 |

Every `*-USD-PERP` row publishes `maxPositionNotional` 2000000000 and
`maxOrderNotional` 10000000 (USD); every `*-USD-KO` row (kind BARRIER, mode
RFQ, `knockOutRebateBps` 1000, `strictTakeProfitPremiumBps` 5000) publishes
`maxOrderNotional` 10000000 (USD) and no `maxPositionNotional`: one request,
one quote and one early-close request is bounded like every other order, while
a member's barrier contract quantity per market is bounded by the base-unit
`maxPositionQty` (section 2a below; the value binds on the sandbox from the
date venue operations announce, on production from the first day). Since
revision 3.71 every `*-USD-KO` row sizes on the quantity grid of the perpetual
of the same underlying with `minNotional` 100 USD (the barrier rows of the
table above): the sandbox re-lists the nine markets whose perpetual is
fractional on the date venue operations announce, symbols unchanged, and takes
the floor in place on the other four; production lists its knock-out markets
(as planned today those of BTC, ETH, SOL, BNB, XRP and JPY, above) on these
values from genesis; `maxOrderQty` and `maxPositionQty` are unchanged by the
re-sizing.
Since revision 3.75 the sandbox lists eighteen more rows on the same rules, the
nine underlyings of the 30 September 2026 listing each with its knock-out
market (HYPE, DOGE, ZEC, NEAR and TAO beside the crypto perpetuals, US500 and
US100 on US equity indices, XAG on silver, and USDJPY, the USD/JPY quanto of
section 2a), from the date venue operations announce; SP500, NDX100, BNB,
BRENT and JPY-USD stay listed beside them. The
five spot rows (revision 3.50, kind SPOT, mode ORDER_BOOK, section 6a) copy the
grid of the perpetual of the same underlying and publish `maxOrderNotional`
10000000 and no position cap: spot has no position, holdings are balances. The
six `*-SWAP` rows (kind SWAP, mode RFQ, revision 3.51, section 6h) have no
price at all: `qty` is a quantity of the ASSET, `minNotional` is the minimum
single take in that asset, and the caps are the base-unit `maxOrderQty` /
`maxPositionQty` only.

**Changing the grid of a listed market: a re-listing (revision 3.83).** The
venue never changes the `tickSize` or the `qtyStep` of a listed market in
place, nor its `contractMultiplier`: every resting order and every open
position is held on the market's grid and valued through its multiplier, so
the venue's listing controls refuse such a change on every market kind, and a
grid change is made only by RE-LISTING the market. A resting order is never
kept at the old granularity, moved onto the new grid or rounded. `minNotional`
is not part of the grid: it may change in place, resting orders stay, and the
new floor applies to new orders, amends and RFQs. A re-listing is
announced in writing in advance, naming the market, the new values and the
date (on the sandbox through the channels of section 1a and a dated revision of
this document). On the date:

1. **Delisting.** `venueEvents` publishes `STOP_TRADING` with `tradingState`
   `DELISTED`, `reason` `DELISTING` and `cancelsResting` true (section 10.9).
   Every working and pending-trigger order on the market is cancelled with
   `cancelReason` `INSTRUMENT_HALT` (on FIX `150=4` with `Text(58)`
   `INSTRUMENT_HALT`), and from then on new orders and amends on it answer
   `3006`, reduce-only orders included: close a position you do not want
   final-settled before the date.
2. **Final settlement.** Open positions are closed at one close-out price, the
   same for every sub-position (section 7); open barrier contracts are
   final-settled within their terms (`terminationReason` `FINAL_SETTLEMENT`),
   and a close request ends with its contract; other open RFQs stay but can no
   longer be quoted or accepted. On a swap market the delisting withdraws every
   standing offer and the open contracts settle as section 6h states.
3. **New listing.** The market with the new values starts with an empty book.
   The announcement names its symbol and `isin`; when it keeps the old symbol,
   that symbol answers `2001` between the removal of the old market and the new
   listing. Every price and quantity off the new grid is rejected with `2002`,
   never rounded.

No field or frame announces a re-listing before the date: `venueEvents` carries
trading-state changes only, so the `STOP_TRADING` frame of the date is the
only `venueEvents` frame a re-listing produces, and the new values appear on
`GET /v1/instruments` and
`get.instruments` from the new listing on. No member surface serves an
instrument id, so key on the symbol; re-read `GET /v1/instruments` after the
announced date and on any unexpected `2001`, `2002`, `3006` or `DELISTED`
status, and carry no cached grid or book state across the date. Item (47) of
revision 3.70 and item (3) of revision 3.71 describe the two re-listings of
this kind so far.

**Position and order size limits.** Both limits are **notional first, with
base-unit fallbacks**: a market may publish `maxPositionNotional` and
`maxOrderNotional` on `GET /v1/instruments`, decimal strings in the settlement
currency (USD on every current listing). Notional denomination gives every
market the same dollar capacity, so smaller markets no longer reach their caps
first in a correlated move.

`maxPositionNotional` is the largest worst-case member net position value the
venue admits on that market. The check is the familiar worst-case one: your net
position, plus your same-direction working orders, plus the incoming order,
valued at the **current mark price**; an order or amend that would push that
value beyond the cap is rejected with code `2011`. Three rules complete the
contract:

- **Risk-reducing flow is never blocked** by the position cap: `reduceOnly`
  orders and venue-initiated closes (liquidation, ADL) always pass, and an
  order whose worst case does not increase your exposure is not gated.
- **No forced reduction on mark drift.** A position that comes to exceed the
  cap only because the mark moved is never forced to reduce; the cap gates
  increases only. Further risk-increasing orders on that market reject with
  `2011` until the valuation is back under the cap, so on a notional-capped
  market a mark move alone can take you to the cap: size against the live mark.
- **Fallback where no notional cap is set.** On a market where
  `maxPositionNotional` is absent or null, the legacy base-unit
  `maxPositionQty` cap applies unchanged, with the same worst-case shape. Where
  a notional cap IS set, it takes precedence and `maxPositionQty` is
  informational legacy.

`maxOrderNotional` is the per-order size cap: the value of one single order,
`price x qty`, must not exceed it or the order rejects with code `2013`. Priced
orders (including the limit leg of conditional orders) are valued at their
limit price; market orders at the current mark; an amend re-checks the FULL
amended quantity at its effective price; an RFQ request is valued at the
current mark at creation and every quote at its quoted entry price (the quote
covers the request's full size). It is a fat-finger guard on a single message,
not an exposure limit, which is why its contract deliberately differs from the
position cap in one respect: **`reduceOnly` orders are NOT exempt**. The guard
applies to closing orders exactly as to opening ones, so closing a large
position may take several orders; that is intended. Venue-initiated closes
(liquidation, ADL) are exempt, so the venue can always close any position.
Where `maxOrderNotional` is absent or null, the legacy base-unit `maxOrderQty`
cap applies unchanged; where it IS set, it takes precedence and `maxOrderQty`
is informational legacy.

**Absolute size ceiling (all markets).** Independently of whichever per-order
cap a market publishes, one order's `price x qty` must stay within the venue's
exact-money range, `1000000000000000000` settlement-currency units. An order
past it rejects `2013`, in both cap arms and on every market kind. This is not a
risk parameter and it is not published per market: it is the bound that lets the
venue represent every trade, fee and position update exactly in integer money,
and it sits orders of magnitude above every `maxOrderNotional` in force and
above any order a member would send. It exists in the contract so the rule is
total, not because you are expected to meet it. The matching guarantee one level
down is a cancel, not a reject: if settling a fill would take a position beyond
that same range, the order is canceled with `cancelReason`
`POSITION_SIZE_LIMIT` rather than settling a position the venue cannot describe
exactly. Neither outcome is reachable at any tradable size.

The venue standard is a `maxOrderNotional` of `10000000` USD (10 million) on
every listing with a price (perpetual, spot and barrier) and a
`maxPositionNotional` of `2000000000` USD (2 billion) on every perpetual
listing; both standards were ratified by venue operations on 10 September
2026. The caps actually in force on a market at any time are the ones it
publishes on `GET /v1/instruments`: a market may publish one cap, both, or
neither (the base-unit caps then apply; the current barrier listing publishes
the order cap only), so read the live values there, never this page.

**Note on XRP-USD-PERP.** With `qtyStep` = `minQty` = 1 XRP, small whole-XRP
quantities price out below the 5 USD floor at typical marks (at a 0.6 mark the
smallest order that clears the floor is 9 XRP). The floor gates orders that
open or grow exposure; partial fills can still leave a position smaller than
that, and such a residual stays closable with an exact-size `reduceOnly`
order as described above.

Per kind:

- **Perpetuals** margin, fees and funding all scale from the same
  `price × qty` notional.
- **Spot** trades on the same fractional grid as the perpetual of the same
  underlying (`qtyStep` `0.0001` on BTC-USD, `0.001` on ETH-USD and BNB-USD,
  `0.01` on SOL-USD, `1` on XRP-USD; revision 3.50): `price × qty` is your
  exact cash leg and `qty` your exact asset leg, with exact math end to end
  (section 6a). Earlier revisions said spot traded whole units (`qtyStep`
  `1`); that described the pre-listing test fixture, never a rule.
- **Barrier** markets quote per base unit like every other market and, since
  revision 3.71, size on the fractional grid of the perpetual of the same
  underlying (`qtyStep` `0.0001` on BTC-USD-KO, `0.001` on ETH-USD-KO,
  BNB-USD-KO, SP500-USD-KO, NDX100-USD-KO and XAU-USD-KO, `0.01` on
  SOL-USD-KO, WTI-USD-KO and BRENT-USD-KO, `1` on XRP-USD-KO, EUR-USD-KO,
  GBP-USD-KO and JPY-USD-KO, and since revision 3.75 `0.001` on ZEC-USD-KO,
  US500-USD-KO and US100-USD-KO, `0.01` on HYPE-USD-KO, TAO-USD-KO,
  XAG-USD-KO and USDJPY-USD-KO, `1` on DOGE-USD-KO and NEAR-USD-KO, the table
  above): the RFQ `qty` is a decimal
  number of units on that step, integer strings stay valid, and `minNotional`
  is 100 USD on every barrier market, applied at create valued at the current
  mark, on every quote valued at its quoted entry price and on a close request
  valued at the contract's whole quantity (section 6b). Earlier revisions
  listed the barrier markets in whole units (`qtyStep` `1`); the sandbox
  re-lists the nine fractional markets on the date venue operations announce,
  and production lists its barrier markets (as planned today those of BTC,
  ETH, SOL, BNB, XRP and JPY, section 2a) on this grid from its first day.

**Compatibility statement.** Prices LOOK unchanged by this model: they were
per coin before and are per coin now. Quantities changed meaning from integer
contract counts to decimal base units; every old integer quantity string
remains a valid quantity. The minimum notional is new: flows that previously
sent very small orders must now respect `minNotional` or handle `2028`. The
model change ships together with a venue data reset (see the note above
section 1).

## 3. Authentication (REST)

Private endpoints require four headers:

| Header | Value |
|---|---|
| `X-API-KEY` | Your API key |
| `X-TIMESTAMP` | Current Unix time in ms |
| `X-RECV-WINDOW` | Optional; the receive window in ms, an integer from `1` to `60000` (default `5000`; any other value is refused `1002`, revision 3.85) |
| `X-SIGNATURE` | Hex HMAC-SHA256 signature (lowercase) |

**API keys, scopes and rotation.** Every credential carries a scope fixed at
issuance: `TRADE` (full capability) or `READ_ONLY` (drop copy; every write is
refused with `1005`, on REST, WebSocket and FIX alike). A member may hold up to
**5 active keys per scope**. Issuing a key ADDS it, and every existing key keeps
working; nothing is ever replaced implicitly. Revocation is explicit and per
key, from the member portal keys card or via venue operations, and takes effect
immediately on every transport: REST requests signed with a revoked key fail
with `1001`, and live WebSocket and FIX sessions authenticated with it are
terminated (on WebSocket the session drops back to unauthenticated without the
socket closing, section 10.6). Your other keys are untouched. Rotate keys as a
failover, not a cutover: issue the new key, migrate traffic, then revoke the
old one, so there is never a moment with zero working credentials. The secret
is shown exactly once, at issuance; the venue stores only what it needs to
verify signatures. Rate-limit buckets are per member and shared across all of
your keys (section 4), so additional keys never change your budget.

**Signature recipe:**

```
signature = hex( HMAC_SHA256( secret, X-TIMESTAMP + METHOD + PATH_WITH_QUERY + BODY ) )
```

- `METHOD`, uppercase HTTP method (`GET`, `POST`, `PUT`, `DELETE`).
- `PATH_WITH_QUERY`, the request path *including* the query string, exactly as sent
  (e.g. `/v1/orders/open?symbol=BTC-USD-PERP`).
- `BODY`, the raw request body bytes as sent, or the empty string for no body.
  The signature covers the exact body bytes regardless of transfer encoding:
  chunked requests and HTTP/2 requests of unknown length are verified against
  the bytes that arrived, exactly like a Content-Length request, and a body
  that is not valid UTF-8 is verified as raw bytes. A conformant client
  (Content-Length request carrying UTF-8 JSON) sees no difference.
  The request body is capped at 1 MiB (1,048,576 bytes); a larger body
  answers HTTP 413 before authentication.

**Worked example** (verify your implementation against this):

```
secret     = my-api-secret
timestamp  = 1700000000000
method     = POST
path       = /v1/orders
body       = {"symbol":"BTC-USD-PERP","side":"BUY","type":"LIMIT","tif":"GTC","price":"65000","qty":"1","clOrdId":"abc-1","clientUid":"CLIENT-42"}

signature  = fe10121dc47151c9f597ab92b71eac96218a14289b048196bccae275edfd6010
```

> **Warning: sign the bytes your HTTP stack actually transmits.** The venue
> verifies the signature against the request path and query string exactly as
> they arrive on the wire. Some HTTP libraries re-normalize percent-encoding
> AFTER you build the URL, so the string you signed is not the string that is
> sent, and every such request fails with code `1001`. The known case is
> Python `aiohttp`/`yarl`, which re-encodes characters such as the commas in
> `channels=execution,margin,order,wallet` (raw comma vs `%2C`). With aiohttp,
> construct the URL with `yarl.URL(url, encoded=True)`, or sign the exact
> final string your client transmits. If your stack sends `%2C`, sign `%2C`;
> if it sends a raw comma, sign the raw comma. The two spellings produce
> different, individually valid signatures; the failure mode is signing one
> and transmitting the other. This bites first on the gap-frame backfill
> query (section 10.7), whose ready-made `channels` list contains raw commas.

**Second worked example**, a GET with a comma-bearing query string, transmitted
with raw commas (not `%2C`), no body:

```
secret     = my-api-secret
timestamp  = 1700000000000
method     = GET
path       = /v1/stream/events?fromSeq=904118&channels=execution,margin,order,wallet&limit=500
body       = (empty)

signature  = 156c00ee0704359d7fc4f40b90208545970afc9d67b4694c7fbf168b7b2d48d3
```

Reproducing this hex proves your signer; it does not prove your transport.
After the hex matches, verify (on the wire, or in your library's request
tracing) that the query string is transmitted byte-identical to the form you
signed. A `1001` on signed GETs that carry a query string, while bodied POSTs
work, is the classic signature of an HTTP layer re-encoding after signing.

Python reference:

```python
import hashlib, hmac, time, json, requests

BASE = "https://sandbox.mtf.perpetuals.net"
KEY, SECRET = "your-api-key", "your-api-secret"

def signed(method: str, path: str, body: dict | None = None):
    body_text = json.dumps(body, separators=(",", ":")) if body is not None else ""
    ts = str(int(time.time() * 1000))
    sig = hmac.new(SECRET.encode(), (ts + method + path + body_text).encode(),
                   hashlib.sha256).hexdigest()
    headers = {"X-API-KEY": KEY, "X-TIMESTAMP": ts, "X-SIGNATURE": sig,
               "Content-Type": "application/json"}
    return requests.request(method, BASE + path, headers=headers,
                            data=body_text or None).json()

print(signed("GET", "/v1/account"))
```

The helper signs every call afresh, which is exactly what the two rules below ask
of a client.

**The receive window (revision 3.85).** `X-TIMESTAMP` is accepted while it lies at
most `X-RECV-WINDOW` milliseconds BEHIND the venue clock and at most the smaller of
`X-RECV-WINDOW` and 5000 ms AHEAD of it; outside that the request is refused with
`1002` (`timestamp outside recv window` when it is too old, `timestamp ahead of the
venue clock by more than the window allows (at most 5000 ms ahead)` when it leads).
The window is checked when the headers arrive and again once the whole body has
arrived, so a request whose body arrives after its window closed is refused like one
that arrived late. `X-RECV-WINDOW` is optional (absent or empty: 5000) and, when sent, must be an
integer from 1 to 60000; any other value (above 60000, 0, negative, not an
integer) is refused with `1002` and `msg` `X-RECV-WINDOW must be an integer from 1
to 60000 (milliseconds); a larger value is refused, not reduced`, before the
signature is checked. The header is NOT part of the signed string (signing it would
break every client that already sends it); the record below is what makes its length
harmless. Synchronise your clock with the venue's (`GET /v1/time`, or NTP): a clock
running ahead of the venue's by more than 5 s is refused whatever window you ask for.

**Every signed write is accepted once (revision 3.85).** The venue records the
(`X-API-KEY`, `X-SIGNATURE`) pair of every signed request whose method is not `GET`
(`POST`, `PUT`, `DELETE`) once its signature verifies, before the request runs, and
refuses the same pair again with HTTP 401, code `1002` and `msg` `request already
received: a signed write is accepted once, sign every request (a retry included) with
a fresh X-TIMESTAMP`; nothing of the refused request runs and nothing is charged. The
pair is kept for 60 seconds past its `X-TIMESTAMP`, the longest window a request can
ask for, so the same bytes are refused for as long as the window could admit them.
When a pair leaves the record, the node also refuses every signed write of the same member stamped at or before it, with `1002` and `msg` `request signed before the oldest one this node still
records: sign it again with a fresh X-TIMESTAMP`, so no pair is ever admitted twice;
you meet this answer only with a stamp more than 60 seconds old or when your own writes fill the record far above every published rate. This applies to every write route (orders, batches, amendments, cancels, RFQ
requests, quotes, accepts and cancels, lot closes, parameters, identity records,
DLT Market instructions). A `GET` is never recorded and may be repeated, and neither is `POST /v1/margin/preview`, which only reads. Two
consequences for a client:

- **Sign every send, a retry included, with a fresh `X-TIMESTAMP`.** A resend of
  identical bytes (an HTTP library's automatic retry, a proxy replay) is refused
  `1002`; signing again is all it takes. Keep the same `clOrdId` on the retried
  request: order entry and, from the matching engine's replay-contract line for this
  revision, RFQ requests and quotes are
  idempotent on it (section 2), which is what tells you whether the first send
  took effect.
- **Two identical writes in the same millisecond are one signature.** Two
  intentionally identical writes (two orders without `clOrdId` and with the same
  fields, the same cancel twice) need different timestamps.

Each venue node keeps its own record; on a clustered deployment only the leader
accepts a write (a follower answers `5030` before it authenticates anything, section 9), so
every write that can take effect passes the leader's record. A node also refuses a
signed write stamped BEFORE it began to accept writes, its start or its promotion to
leader, with `1002` and `msg` `request signed before this node began to accept
writes: sign it again with a fresh X-TIMESTAMP`, because what it accepted before
then is not in its record: a request you held across a venue restart or a leader
change is signed again. A client whose clock runs AHEAD of the venue's leaves, around
a restart or a leader change, a window of at most that lead (5 s at most); around a
leader change the window also takes in any difference between the clocks of the old
and the new leader node. With a clock synchronised to the venue's, only that
difference remains.

**Migration table, revision 3.85** (dated 20 October 2026; corrections in force at
publication by the operator's decision of 2026-10-20, the venue being before
production go-live, the 30-day notice of the versioning policy waived for them; this
table is the guide of what to do; the WebSocket login has its own table in section
10.1):

| What you do | Before revision 3.85 | From revision 3.85 | What to do |
|---|---|---|---|
| send a signed write again with the same `X-TIMESTAMP`, `X-SIGNATURE` and body inside the window | accepted and executed again | HTTP 401, `1002`, `request already received ...`; nothing runs | sign every send, a retry included, with a fresh `X-TIMESTAMP` and keep its `clOrdId` |
| send a signed write stamped before the venue node you reach started or became the leader | accepted while inside the window | `1002`, `request signed before this node began to accept writes ...` | sign it again |
| send `X-RECV-WINDOW` above 60000 | reduced to 60000 | `1002` with the `X-RECV-WINDOW` sentence | send at most 60000, or no header (5000) |
| send `X-RECV-WINDOW` of 0, a negative value or a value that is not an integer | 0 and negatives reduced to 0 (every request refused `1002`), a non-integer replaced by 5000 | `1002` with the `X-RECV-WINDOW` sentence | send an integer from 1 to 60000, or no header |
| stamp `X-TIMESTAMP` more than 5000 ms ahead of the venue clock | accepted up to the window ahead | `1002`, `timestamp ahead of the venue clock ...` | synchronise your clock with the venue's |
| send an RFQ request or a quote with a `clOrdId` you already used | admitted: a second request opened, or the quote replaced your later quote | `2009`, nothing opened or replaced (section 6b; from the matching engine's replay-contract line for this release) | a new `clOrdId` for every new request and quote; after a lost answer read `GET /v1/rfq/mine` or `GET /v1/rfq/quotes/mine` |

## 4. Rate limits

Two INDEPENDENT token buckets per MEMBER: **6000 requests/min** for reads and
**40000/min** for the order budget. The buckets are member-level and shared
across every API key the member holds (previously documented as per API key;
with multi-key credentials, section 3, per-key buckets would multiply the
budget by the number of keys issued, so the budget attaches to the member).
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. Order-budget traffic does NOT
consume a read token, so heavy order flow never exhausts your read allowance
and heavy polling never throttles your order flow. Breach returns HTTP 429 with
code `1003`.

The two public candle routes of section 5 (`GET /v1/klines` and
`GET /v1/mark-klines`, no authentication) carry their own gate since revision 3.70,
independent of your member buckets: a per-peer window and a venue-wide concurrency
budget for candle aggregates in flight. Beyond either the route answers HTTP 429
with code `1003` and `retryAfterMs` (plus `Retry-After`), before any work is done;
sleep the wait and retry. Neither touches your order flow or your authenticated
reads.

**Repeated engine faults (revision 3.79).** A command the matching engine aborts
on answers `5005` (section 9), and the venue verifies its books after every such
abort, which costs every venue node time. When ten of your commands answer
`5005` within 60 seconds, every request of yours that consumes the order budget,
on every key and channel, answers HTTP 429 with code `1003` and `retryAfterMs`
until the oldest of those ten is 60 seconds old, so the wait can be up to 60,000
ms here. A request refused this way does nothing and consumes no order-budget
unit (a barrier early close spends one read unit, see below). 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: a reduce-only order (`reduceOnly` true on
`POST /v1/orders` and the WebSocket `order.place`, FIX `9001=Y` on `35=D`), a
placement batch (`POST /v1/orders/batch`, WebSocket `orders.batch`) whose every
entry is reduce-only, a lot close (`POST /v1/lots/{lotId}/close`) sent with
`reduceOnly` true, and a barrier early close: the holder's close request
(`POST /v1/rfq` with `closeContractId`), the writer's quote on that close
request and the holder's accept of that quote. Such a request still consumes
its order-budget unit, and the engine still rejects a reduce-only order that
would increase a position. At the budget the venue reads whether such an early close is your own (a close request naming an open contract you hold, a quote on a close request of a contract you wrote, an accept of a quote on a close request of a contract you hold), and that read consumes one unit of your read budget whatever its outcome; any other, another member's contract or close request or one that does not exist, answers `1003`, and so does your own while your read budget is empty, because the venue then does not read. While the venue sheds order entry it does not read, and the request answers `5001`; it also answers `5001` when the read meets a full engine queue, after the read unit is spent; both are the answers below the budget while order-budget units remain. An amendment, single or batch, is still refused, and
so is a placement batch with even one entry that is not reduce-only (the whole
batch is refused). A `5005` is a fault of the
venue: never resend such a command unchanged, and tell venue operations which
command it was. Such a refusal names its cause: `msg` reads `rate limit:
repeated engine faults (5005)` on REST and in the WebSocket ack, and FIX
`Text(58)` begins with the same words before the wait (every other `1003` keeps
its text; both begin with `rate limit`). When the budget trips, the venue mails
your trading contact on file (without one, your incident or compliance contact)
once. Ten `5005` answers in 60 seconds is a published rule of the venue:
production runs exactly these values on every node.

**Reads of the venue's history store (revision 3.73).** The event stream routes
of section 11 (`GET /v1/stream/events`, `GET /v1/stream/cursor` and its WebSocket
twin `get.streamCursor`, `GET /v1/margin/calls`) and the history routes
`GET /v1/executions`, `GET /v1/orders/history`, `GET /v1/account/ledger`,
`GET /v1/adl/events` and `GET /v1/reconciliation/end-client-positions` are served
from the venue's history store. A read the store could not answer answers HTTP
503 with code `5007` and `retryAfterMs` (plus `Retry-After`), where it answered
HTTP 500 with code `5000` before this revision. Since this revision four
technical bounds apply beside your read bucket, on each node of the venue that
serves you: a bound on how many reads of yours of each of the two groups run at
once, a budget of these reads in flight on the node that every member shares, a
bound on how long one read may run, and a yield to the venue's own writes to the
store. The event stream group covers every stream reader of yours, all keys and
all filters together: combine your filters into one reader or serialise your
readers. A read beyond the in-flight bound or the shared budget waits briefly
for its turn and, if none frees, answers HTTP 429 with code `1003` and a
SUGGESTED `retryAfterMs` (a turn of the in-flight bound frees when another read
of yours ends, a turn of the shared budget when any member's read on the node
ends: a `1003` while no other read of yours of the group is in flight comes from
the shared budget, which serialising cannot help, so sleep the wait and back off
if it repeats); a read that exceeded the time bound, a read of a group whose
previous read just exceeded it (the group is held for `retryAfterMs`) and a read
while the store is catching up with the venue answer HTTP 503 with code `5007`.
Nothing changed on your account in any of these cases, and the read token was
spent. Keep one request in flight
per stream, wait for its answer before you send the next, set your client
timeout to at least 15 seconds, and back off on repeated `5007`; when the SAME
read answers `5007` again and again, narrow it (a smaller `limit`, a later
cursor or `from`, no `symbol`). While the store has a recorded hole
(`store.status` `REPAIRING`, section 11.3), `GET /v1/executions` and
`GET /v1/account/ledger` serve only the rows at or below the store watermark and
answer it as `asOfSeq`, with and without a `symbol` or `kind` filter, as the event
stream routes and the rejection rows of `GET /v1/orders/history` do (the two
since revision 3.83; before, they could carry rows from after the hole while
the hole's own rows were missing). The closed-order rows of
`GET /v1/orders/history` are not held: until the status is `COMPLETE` again
they can include orders closed after the hole, above `asOfSeq`, while orders
closed inside it are missing.

A placement batch (`POST /v1/orders/batch`, section 6c) and, since revision
3.74, an amendment batch (`POST /v1/orders/batch/amend`) are charged N units up
front, all or nothing: if fewer than N units are available the whole batch
answers `1003` with `retryAfterMs` and consumes nothing. The amendment batch is
charged after the venue's engine-busy check, so its engine-busy `5001` consumes
nothing either; a placement batch is charged first, so its engine-busy `5001`
and its order-to-trade `1004` at the envelope leave the N units spent. The units
of a batch whose entries are refused after the charge (an order that is not
found or not yours, a market where the order-to-trade regime blocks you, a grid
refusal) stay spent, as a single amendment's unit does. A cancellation batch
(`POST /v1/orders/batch/cancel`, revision 3.74) consumes no unit and is never
throttled.

Both buckets are full-capacity and refill continuously, so after a quiet period the
whole minute's allowance is available at once. The order bucket is sized for a DMA
member that must push a burst of risk-reducing orders when many end clients
liquidate in a short window.

WebSocket and FIX order entry share the order-entry bucket, and so does the
member web portal's DLT Market order ticket (section 6g, revision 3.70: one bucket
per member, the portal session is one more credential); WebSocket `get.*`
requests share the read bucket. Cancels consume neither bucket (they still count
toward the order-to-trade regime). Limits are per environment. Since revision 3.61
a member-specific value is a CLASS matter: the venue publishes its classes and
their objective criteria on the Website as a Rules annex before any value is
written, a member's own limits can only ever be a published class or a value
tighter than its class, and every member reads its own effective rate-limit and
order-to-trade values, the class it sits in and every other parameter the venue
applies to it on `GET /v1/account/parameters` (section 7).

**The rate-limit class ladder (revision 3.62).** Every member sits in one published
class of the ladder: `STANDARD` (the venue defaults above, every member without an
assignment) and the classes the venue publishes beside it with their objective
criteria (a `DMA` class for members providing direct electronic access under the
Company's DEA approval, Rules section 8; an `MM` class for designated Market Making
Members, a signed Market Maker Agreement and an active Market Maker Scheme
enrolment; and others as published). Since revision 3.70 the criteria of a class
are checked for every member at the assignment, the operator's own group included.
Each class carries a per-MINUTE rate for reads and for orders AND a per-SECOND burst
ceiling for orders (`ordersPerSecBurst`): where a class publishes one, a second
continuous-refill bucket of that capacity binds beside the per-minute bucket, so a
burst above it answers `1003` with a `retryAfterMs` under a second even while the
minute's allowance is far from spent (RTS 7 Article 18 is a limit per second). A
class with `0` publishes no per-second ceiling and the per-minute bucket alone binds.
A raise is only ever a class assignment; a member-specific number is only ever
tighter than the class (`source` `MEMBER` on the read). A tighter member-specific
value, or a move to a lower class, is a Rules measure (revision 3.65): venue
operations record an objective ground (Rules section 9 or 36, or the class ladder)
and a case reference, and you are notified by mail with the section 36 appeal route,
exactly as for a parameter measure (section 7). Your class and the values in
force are `account.rateLimits` on `GET /v1/account/parameters` and on the portal.

**The published ladder (revision 3.70).** The classes in force, their criteria and
their numbers:

| Class | Criteria | Reads per minute, orders per minute, orders per second |
|---|---|---|
| `STANDARD` | every member without an assignment | 6,000, 40,000, 1,000 |
| `DMA` | a Member the Company has approved to provide direct electronic access (Rules section 8) whose DEA flow is marked `reg.dea` | 12,000, 120,000, 3,000 |
| `MM` | a Market Maker Member with a signed Market Maker Agreement (Rules section 3) | 12,000, 200,000, 5,000 |

Every class's per-second ceiling is one fortieth of its per-minute order rate. The
STANDARD per-minute pair is unchanged; the 1,000 orders/s ceiling is the only change
to STANDARD, announced at least seven Business Days before it takes effect with the
three Business Day comment right of Rules section 43. Limits are per environment: the
production venue publishes these numbers; the sandbox publishes no per-second ceiling
(`ordersPerSecBurst` 0) until its bot flow is measured.

**Order-to-trade ratio.** The venue operates an order-to-trade regime (RTS 9). Since
revision 3.70 the ratio is measured PER MEMBER PER MARKET on the order-book markets
(`account.otr.basis` reads `PER_INSTRUMENT`): every order message you send on a
market (a new order, a batch entry, an amendment, and a cancel you asked for, one
per cancelled order, on the market it cancels on) and every ordinary fill you get
there (a continuous-trading execution or a call-auction execution; liquidation, ADL
and reversal executions are not counted) count on that market's own ring over a
sliding window of `windowMinutes` (60,
published), and the ring only binds after `minOrders` order messages in the window
(100, published; both on `account.otr` of `GET /v1/account/parameters`); with no
trade in the window the ratio is the message count over the floor. The published
window is 60 minutes and the published floor 100 order messages, unchanged since
revision 3.62. On the production venue the published warning thresholds for the
first quarter are STANDARD 500 and MM 5,000 with no block threshold; block
thresholds follow by annex amendment on the seven Business Day track once a
quarter of measured ratios exists; the sandbox publishes no threshold. Every member
sits in a published OTR class (`STANDARD` carries no threshold where none is
published); a member-specific
threshold is only ever LOWER than its class, and the class threshold applies to
each market's ratio on its own. Exceeding the block threshold on a market rejects
new orders, batch entries and amendments on THAT market with HTTP 429 and code
`1004` until its window slides below the threshold; your other markets are
unaffected; a placement batch is gated per market as a whole (any entry on a
blocked market refuses the whole batch, nothing is counted); an amendment batch
(revision 3.74) answers `1004` for each entry on a blocked market, counts nothing
for it and runs its other entries, and each of its entries counts as one order
message on its order's market when the venue hands it to the matching engine;
cancels always pass. Your figures per
market are `markets[].otr` (`orders`, `trades`, `ratio`, `state` OK, WARN or BLOCK
for your next message there, and the window), present on order-book markets only:
RFQ quote messages and RFQ-mode markets are not counted. The account-wide ratio over
every market's messages is a warning flag to venue operations only
(`account.otr.state`, OK or WARN, never a block). A lower warning threshold flags
the member without rejecting. No order-to-trade surcharge applies. The published
schedule carries a prepared OTR surcharge row per class (`account.otr.surcharge`
`{enabled, bps}`) that is NOT IN FORCE: `enabled` is false and the row is applied
to nobody. The venue may introduce a surcharge for members whose ratio of order
messages to executed transactions exceeds the published threshold, measured over
the published window and cycle, only by an amendment of the fee schedule announced
at least seven Business Days before it takes effect; the venue announces its
activation on the schedule before it applies, and until then the row is a
publication carrier only.

**Direct electronic access controls (revision 3.62).** An order you tag `reg.dea`
(FIX tag `9026=Y`) is the flow of a client you give direct electronic access to, and
the venue can control that flow separately from the rest of yours (MiFID II Article
48(7), Rules section 8): a DEA-flow class publishes a count and a notional of
`reg.dea` orders per member per calendar day (UTC; `STANDARD` carries no limit), and
venue operations can STOP one DEA client on your account by identifier (the
`clientUid`, or the `reg.traderId` carrying the flow) as a measure with a stated
Rules ground and a case reference, notified to you by mail with the section 36
appeal route. A `reg.dea` order of a stopped identifier answers `3010` on REST, the
WebSocket ack and the FIX report alike, before anything is sequenced; a `reg.dea`
order past the day's flow allowance answers `3011`. Your unmarked orders, and the
DEA flow of your other clients, are never touched by a stop; working orders of a
stopped identifier are yours to cancel. A tighter member-specific DEA-flow value, or
a move to a lower DEA class, is a Rules measure (revision 3.70): venue operations
record an objective ground (Rules section 9 or 36, or the class ladder) and a case
reference, and you are notified by mail with the section 36 appeal route, exactly
as for a rate-limit measure above. The controls in force, today's use and the
stopped identifiers are `account.dea` on `GET /v1/account/parameters`; the day's use
is kept by the venue across a restart and resets at 00:00 UTC.

## 5. Public market data (no authentication)

| Endpoint | Description |
|---|---|
| `GET /v1/time` | Server time |
| `GET /v1/status` | Venue + per-instrument status. Legacy `venue`/`status` values are unchanged; the additive `venueTradingState`/`tradingState`, `reason`, `since` and `expectedResumeTs` fields use the `venueEvents` vocabulary (section 10.9) |
| `GET /v1/instruments` | Instrument specifications: `kind` (PERP \| SPOT \| BARRIER \| SWAP \| SECURITY, the last a DLT financial instrument of section 6i, prefunded like SPOT), `mode` (ORDER_BOOK \| RFQ, section 6b), `tickSize` (per base unit), `qtyStep`/`minQty`/`minNotional` (section 2a), `contractMultiplier` (the contract value, section 2a; `"1"` on every listed market, fixed at 1 on BARRIER markets), order and position limits (`maxOrderNotional`, `maxPositionNotional`, legacy `maxOrderQty`/`maxPositionQty`, section 2a), fees, risk tiers, the `funding` schedule block (`schedule` GRID \| LOCAL_DAILY, zone, window, next settlement and snapshot times, the rate band `rateCap` / `rateFloor` the settled rate is clamped to, where `rateCap` never exceeds 1 and `rateFloor` never lies below -1, revision 3.70; section 7; omitted on spot and barrier), bands (`priceBandBps`, reject 2003 beyond it, and `marketMaxSlippageBps`, the market-order protection collar around the best eligible price, section 6), `isin`/`cfi` reference data, and on BARRIER markets the economics pair `knockOutRebateBps` / `strictTakeProfitPremiumBps` (revision 3.45, section 6b; omitted on perp and spot), and on SWAP markets (revision 3.51, section 6h) `baseAsset` (the asset), `swapMinTenorDays`, `swapMaxTenorDays` and `swapMaxRateBps` (omitted on the other kinds). Since revision 3.61 EVERY rule of the market the venue enforces is published here (Rules 13.8: order limits are notified via the Website AND the API): `markJumpRejectBps` (the mark-jump guard), `staleMarkTimeoutSec` (the staleness timeout of section 10.9), `circuitBreaker` `{bps, windowSec, cooldownSec}` (the own-traded-price breaker), on PERP rows `liquidation` `{slicePercent, minSliceQty, aggressionBps}` each with a `...Source` of `MARKET` or `VENUE` (section 7, the liquidation waterfall), `funding.rateCap` / `funding.rateFloor` (the band the settled rate is clamped to) and on GRID markets `funding.rateSource` `TICK` \| `FIXED` with `funding.fixedRate` under FIXED, `maxOpenOrdersPerMember` (the per-market open-order ceiling, omitted when the market carries none), `accessMode` `OPEN` \| `ADMISSION` (section 7, your effective parameters; since revision 3.70 the six swap markets read `ADMISSION`, section 6h), `tradingHours` (revision 3.70: `CONTINUOUS` on every listing except a SECURITY market with an `auction` block, which reads `SCHEDULED`, its hours being the block, section 6j; otherwise 24 hours a day, every calendar day, no daily open, close or auction, interruptions announced on `GET /v1/status` and the `venueEvents` channel; open set; the Trading hours paragraph below), on BARRIER rows `barrierGuard` `{entryBandBps, minTakeProfitBps, minBarrierBps}` (the quote guard of section 6b, 50 / 10 / 10 unless the market sets its own), `strictTakeProfitMaxLifeHours`, since revision 3.70 `maxContractLifeHours` (the longest life of an OPENING contract in whole hours, measured from the request, section 6b; absent where the market sets none, an omitted `barrier.expiryTs` being open-ended only there) and, since revision 3.70, `barrierExpiryRequired` (true where the venue lists DATED barrier contracts only, section 6b; false on the sandbox; absent on other kinds), and on SWAP rows `swapOfferValiditySec` `{min, max}` and, since revision 3.70, `swapWriterEligibility` (`MARKET_MAKER` while only designated Market Maker Members may post offers on their own account, `3013` otherwise; `ANY` while the sandbox has not activated the rule; an open set; section 6h). Since revision 3.70 every row may carry `pendingFees` `{makerFeeRate, takerFeeRate, effectiveFrom, effectiveFromIso}`, the ANNOUNCED change of the market's default maker and taker rates and the instant it applies (at least seven Business Days after the Website announcement, section 7); absent while no change is pending. On SECURITY rows that carry a call auction schedule (revision 3.67, section 6j): `auction` `{schedule, zone, openCallStart, openUncross, closeCallStart, closeUncross}` (local wall times `HH:MM`), the live `tradingPhase` (`CONTINUOUS` \| `CALL_OPEN` \| `CALL_CLOSE` \| `CLOSED`) and, after the first uncross that traded, `lastAuctionPrice` and `lastAuctionTs` (Unix ms); a SECURITY market without a schedule carries none of the four. Every enumeration is an open set |
| `GET /v1/orderbook?symbol=&depth=` | Full L2 book (depth 1/5/25/100). The `checksum` covers the levels the snapshot carried, capped at the top 25 per side (so `min(depth, 25)`) |
| `GET /v1/trades?symbol=&limit=` | Recent public trades (anonymous, with LIQUIDATION/ADL/RFQ/BARRIER_CLOSE/SWAP/REVERSAL flags; on a swap market `price` is the rate in basis points) |
| `GET /v1/ticker?symbol=` | Best bid/ask, last, mark, index, funding, open interest |
| `GET /v1/klines?symbol=&interval=&from=&limit=` | OHLCV candles for charting. `interval` one of 1m/5m/15m/30m/1h/4h/1d (default 1h), `limit` 1 to 1000 (default 200), rows oldest first. `from` (Unix ms) is bounded to the most recent `limit` intervals |
| `GET /v1/mark-klines?symbol=&interval=&from=&limit=` | Mark-price candles (revision 3.70): the venue mark folded per interval from its one-minute mark history, the same `interval` set, `limit` and `from` bound as the klines route, rows oldest first `{t, open, high, low, close, ticks}` (`ticks` = mark updates in the bucket; no volume because the mark is not a trade). This is the series a perpetual is margined and funded on and a barrier contract is valued, knocked out and takes profit on; a market without a mark (a swap market, a market never marked) answers an empty array |
| `GET /v1/index-price?symbol=` | Index price |
| `GET /v1/mark-price?symbol=` | Mark price |
| `GET /v1/funding/current?symbol=` | Current + predicted funding rate, next settlement time, and the `funding` schedule block (section 7). On a LOCAL_DAILY market the predicted fields are omitted before the day's window opens |
| `GET /v1/insurance-fund` | Insurance fund state (`enabled` flag; this MTF runs it disabled, so `enabled` is false and balances are 0) |

**Trading hours (revision 3.70).** Every market on the Platform other than a
SECURITY market with an auction schedule (section 6j) trades continuously, 24
hours a day, every calendar day of the year, weekends and public holidays
included. There is no daily open, close or auction on those markets. Trading in
a market is interrupted only by a suspension, halt or removal under Section 11
of the Rulebook (including a venue-wide suspension), by the circuit breaker
under Section 13 of the Rulebook, or by the reduce-only and post-only sessions
described in this document (the `REDUCE_ONLY` and `POST_ONLY` trading states of
section 10.9). The Company operates no daily or weekly closure. The only
scheduled interruption is the venue's standard maintenance window, Saturday
02:00 to 06:00 UTC, published under this section as a standing exception to the
trading hours; maintenance inside it is announced at least 48 hours in advance
on the Website and in the member portal. Most releases are rolling and need no
window; a release that needs one keeps the restart contract of section 10.7
(advisory frame, close 1001, 503 with Retry-After, FIX Logout, cursor
backfill). A release that requires the venue to stop is announced on the
Website at least five Business Days before the window (Business Day as defined
in the Rulebook), naming the date, the start time in UTC and the expected
duration. A scheduled closure outside that window is published on the Website
in advance under this section with the notice the Rulebook requires for a
material amendment; an unplanned stop is a suspension under Section 11 of the
Rulebook. The current trading state of each market is published at all times
on `GET /v1/status`, `GET /v1/instruments` and the `venueEvents` channel.
Funding on perpetual contracts settles on the schedule published per market
(an 8 hour UTC grid, or one local-time window per calendar day for the
daily-settled markets) whether or not the underlying reference market is open.
Daily allowances and daily flow statements are kept per UTC calendar day. A
connection interruption during a software release (the venue restart advisory,
section 10.7) is not a closure: orders, positions and contracts persist and the
market resumes in the trading state it had. Every instrument row on
`GET /v1/instruments` and WS `get.instruments` carries `tradingHours`,
`CONTINUOUS` on every listing without an auction schedule and `SCHEDULED` on a
SECURITY market with one (section 6j); the set is open.

Example, `GET /v1/orderbook?symbol=BTC-USD-PERP&depth=5`:

```json
{
  "code": 0, "msg": "ok",
  "data": {
    "symbol": "BTC-USD-PERP",
    "bids": [["64999.5", "12"], ["64999", "3"]],
    "asks": [["65000", "7"], ["65000.5", "20"]],
    "checksum": 2016409727
  },
  "asOfSeq": 1041, "ts": 1700000000123
}
```

The `checksum` is a CRC32-C over the canonical string of the levels this snapshot carried, capped at the
top 25 per side (so `min(depth, 25)`). At `depth` 1, 5 or 25 it covers exactly the rows you received; a
`depth=100` snapshot returns 100 rows but its `checksum` still covers only the top 25, so verify a
snapshot against the rows you hold (at most 25), never against all 100. WebSocket DELTAS are different:
their checksum is always the top 25 of the full book, so continuous delta verification needs a
subscription depth of 25 or 100. See section 10.4 for the canonical string, which is shared byte for
byte by REST snapshots and WebSocket frames.

Example, `GET /v1/klines?symbol=BTC-USD-PERP&interval=1h&limit=3`:

```json
{
  "code": 0, "msg": "ok",
  "data": [
    { "t": 1700000000000, "open": "64980", "high": "65010", "low": "64950", "close": "65000", "volume": "12.5", "trades": 42 },
    { "t": 1700003600000, "open": "65000", "high": "65120", "low": "64990", "close": "65080", "volume": "9.1", "trades": 31 },
    { "t": 1700007200000, "open": "65080", "high": "65200", "low": "65050", "close": "65150", "volume": "15.0", "trades": 55 }
  ],
  "asOfSeq": 1041, "ts": 1700000000123
}
```

Each row is one candle: `t` is the bucket open time (Unix ms), `open`/`high`/`low`/`close` are
trade prices in that bucket by engine sequence, `volume` is the summed traded quantity and
`trades` is the number of trades. `interval` must be one of 1m/5m/15m/30m/1h/4h/1d (any other
value answers 400), and `from` only pulls back at most `limit` intervals, so a call never
returns more than `limit` candles ending at the present.

Example, `GET /v1/mark-klines?symbol=BTC-USD-PERP&interval=1h&limit=3` (revision 3.70):

```json
{
  "code": 0, "msg": "ok",
  "data": [
    { "t": 1700000000000, "open": "64980", "high": "65010", "low": "64950", "close": "65000", "ticks": 60 },
    { "t": 1700003600000, "open": "65000", "high": "65120", "low": "64990", "close": "65080", "ticks": 60 },
    { "t": 1700007200000, "open": "65080", "high": "65200", "low": "65050", "close": "65150", "ticks": 58 }
  ],
  "asOfSeq": 1041, "ts": 1700000000123
}
```

Each row is one mark-price candle: `open` is the first minute's opening mark in the bucket,
`close` the last minute's closing mark, `high` and `low` the extremes and `ticks` the number
of mark updates the bucket saw; there is no `volume` and no `trades` because the mark is not
a trade. The interval set, the `limit` range and the `from` bound are exactly those of the
klines route, and an unknown symbol answers `2001`.

## 6. Trading (private)

**Trading hours.** Every market other than a SECURITY market with an auction
schedule (section 6j) trades continuously, 24 hours a day on every calendar
day, including days on which an underlying market is closed; there is no daily
open or close on those markets (the Trading hours paragraph of section 5 states the
rule, the maintenance window and the trading states that interrupt it). Trading
on a market stops only under the halt and suspension rules (circuit breaker,
stale mark, administrative halt, delisting, member suspension: the Volatility
safeguards subsection below, section 10.9 and `GET /v1/status`), and the
trading state of every market is published through the API at all times.

**Executions are anonymous.** An execution record, on `GET /v1/executions`,
the WS `execution` channel, the FIX drop copy and the event stream of section
11, names your own side only: your order, your `clOrdId`, your `clientUid`,
your fee and your role. No field identifies the member on the other side, and
the venue does not disclose it except where Applicable Laws or the Rules
provide for it.

### Place order, `POST /v1/orders`

```json
{
  "symbol": "BTC-USD-PERP",
  "side": "BUY",
  "type": "LIMIT",
  "tif": "GTC",
  "price": "65000",
  "qty": "0.25",
  "reduceOnly": false,
  "clOrdId": "your-unique-id-1",
  "clientUid": "CLIENT-42",
  "reg": {
    "tradingCapacity": "MTCH",
    "clientIdType": "LEI",
    "clientId": "529900T8BM49AURSDO55",
    "investmentDecisionId": "ALGO-7",
    "executionDecisionId": "TRADER-3",
    "traderId": "TR-12-9F3A21BC",
    "dea": false,
    "liquidityProvision": false
  }
}
```

- `side`: `BUY` | `SELL`
- `type`: `LIMIT` | `MARKET` | `STOP_MARKET` | `STOP_LIMIT` | `TAKE_PROFIT_MARKET` | `TAKE_PROFIT_LIMIT`.
  **Trailing stops are not offered**; any trailing-stop parameter is rejected with code `2004`.
- `tif`: `GTC` | `IOC` | `FOK` | `POST_ONLY` | `GTD` (with `expireTs`, ms). Market
  orders execute as IOC within the protection collar and never rest.
  A `GTD` order never trades at or after its `expireTs` (revision 3.86), measured
  against the time the venue sequences each command, never your clock: from that
  instant an incoming order or a call auction's uncross that reaches it expires it
  instead of trading it (status `EXPIRED`, exactly as the venue's expiry sweep reports
  it), a conditional `GTD` order reaching its trigger price expires instead of
  triggering, and an amendment or a cancellation of it answers `2014` and expires it (a cancel-all expires it among the orders it ends). An order nothing
  reaches is reported `EXPIRED` by that sweep, which runs about once a second, so the
  `EXPIRED` report can arrive up to about a second after `expireTs` while no fill can.
  Before revision 3.86 the sweep alone expired a `GTD` order, so it could still trade
  until the sweep reached it, up to about a second after its `expireTs` and longer
  while the venue's timers lagged (migration table below "Manage orders"). 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.
- Conditional orders add a `trigger` object:

```json
"trigger": { "source": "LAST", "price": "67000", "direction": "AT_OR_ABOVE", "limitPrice": "67100" }
```

  `source`: `LAST` | `MARK` | `INDEX`. `limitPrice` is required for the `*_LIMIT`
  types (the resting price after triggering). Triggered orders enter matching in
  strict (`triggerPrice`, arrival) order. The activation is reported on the
  WebSocket `order` channel (`OrderTriggered`), in the event stream
  (`ORDER_TRIGGERED`) and, since revision 3.78, on FIX as an ExecutionReport
  with `ExecType(150)=L` (FIX rules of engagement section 4).
  **No price band and no collar on a conditional order** (revision 3.83 states
  it; nothing changed, kept by venue decision of 20 October 2026): neither the
  trigger `price` nor the `limitPrice` is checked against the price band
  (`priceBandBps`, `2003`), at placement, on amend or at activation, so a
  `STOP_LIMIT` or `TAKE_PROFIT_LIMIT` is accepted, and its triggered limit child
  rests and trades, at prices a direct `LIMIT` order is refused at. A triggered
  limit child is not collared either: the protection collar
  (`marketMaxSlippageBps`) applies to `MARKET` orders and to the children of
  `STOP_MARKET` and `TAKE_PROFIT_MARKET` only, so a limit child trades through
  the book up to its `limitPrice`, however far that lies from the mark. Choose
  the `limitPrice` as the worst price you accept at the moment the trigger
  fires: a `limitPrice` far through the market makes the child a market order
  without the collar.
- `reduceOnly: true` orders can never increase your MEMBER position; your
  reduce-only orders, resting ones and untriggered conditional ones alike, are
  automatically trimmed, newest first, or canceled (`REDUCE_ONLY_VIOLATION`) if
  your member position shrinks; a trim arrives unsolicited as `OrderAmended`
  with the reduced `qty` and `remainingQty` (on FIX `150=5`). **The budget is member-level, never client-level. Read the warning
  below before using this flag to close an end client's position.**
- `positionSide` (optional, hedge mode): `LONG` | `SHORT`. Routes this order's
  fills to that sub-position bucket of the order's `clientUid`, so one client can
  hold a long and a short sub-position in the same instrument at once; see the
  hedge mode subsection below. ABSENT means netting, exactly as before the field
  existed; the default is unchanged and the field is strictly additive.
  Order-book perpetual markets only: on a spot instrument it rejects with `2016`,
  a value other than `LONG` or `SHORT` rejects with `2013`, and RFQ and barrier
  flows do not take the field. The same field and semantics apply on the WS
  `order.place` op, on every batch entry (section 6c) and on FIX (venue tag
  `9016`). Echoed on the ack, on open-order reads and on order events, and
  OMITTED (not null) for netting orders, so pre-hedge payloads are unchanged
  byte for byte.
- `clOrdId`: your order reference, echoed on every response and notification for the
  order's lifecycle. The `KX-` prefix (any casing) is reserved for venue-generated
  references and is rejected with code `2020`.
- Instrument-state rejects are distinct codes: `3003` instrument halted (temporary,
  retry after resume), `3005` no mark price yet (wait for the feed), `3006`
  instrument delisted (permanent for that listing; after a re-listing the same
  symbol can return as a new market, section 2a), and, since revision 3.70, `3014` GRID funding
  catch-up pending: the market's funding settlement is more than 60 s overdue and
  the venue is settling the missed anchors, so an opening order, a non-reduce-only
  amend and an RFQ accept are refused until the catch-up lands (retry after the next
  `FUNDING_SETTLED` event, or once `GET /v1/funding/current` `nextFundingTs` lies in
  the future; the same text on REST, the WebSocket ack and FIX `Text(58)`).
  Reduce-only orders stay admitted, and a conditional order whose trigger fires
  inside that window is canceled with `cancelReason` `FUNDING_CATCH_UP` instead of
  executed (section 7, "Catch-up fence").
- `reg` (optional but expected for MiFID II members): regulatory order-record fields
  per RTS 24/25 and RTS 22. `tradingCapacity`: `DEAL` (own account) | `MTCH` (matched
  principal) | `AOTC` (any other capacity). `clientIdType`: `LEI` | `NATIONAL_ID` |
  `INTERNAL`. `investmentDecisionId` / `executionDecisionId` identify the algo or
  person responsible (Art. 26(3) MiFIR). `dea` flags direct electronic access;
  `liquidityProvision` flags market-making activity; `riskReducing` (boolean,
  revision 3.70, absent = false, only meaningful on commodity-derivative markets,
  RTS 22 field 64) declares that the transaction objectively reduces risk, and
  counts toward the 512 byte reg ceiling like every other key. These fields are
  journaled with the order, immutable, and returned in your regulatory extracts,
  they never affect matching. On FIX the same fields ride venue tags `9021` to
  `9027` and `9029` on NewOrderSingle (`9012` continues to carry `reg.traderId`);
  an absent tag leaves the field unset exactly as omitting the key here,
  validation is shared with REST and WebSocket, and the journaled regulatory
  record is byte-identical across all three transports. See the FIX rules of
  engagement.
- `reg.traderId` (rulebook 5.10 to 5.13): the venue-issued Trader ID of the
  Authorised User entering the order. Enforcement is **staged per member**: while
  you have no registered Authorised Users nothing is gated, and from the moment
  your first one is registered every order must carry a registered, active Trader
  ID or it is rejected with `2021`. The check runs on REST, WebSocket and FIX
  alike (on FIX the id rides venue tag `9012` on NewOrderSingle) and, since
  revision 3.70, on the member web portal's RFQ functions (requests, quotes and
  acceptances, including swap offers and takes), where the RFQ tab carries a
  Trader ID field; cancels are never gated on any channel. The check is applied
  before the engine is reached, so a rejected order is never placed. Margin
  previews are exempt on every transport. Treat the id as an opaque string of up
  to 64 characters; venue operations issue it, by default in the form
  `TR-{memberId}-{8 hex characters}`.

> **Warning: `reduceOnly` is measured against your MEMBER NET position, never
> against an end client's position.** The venue's risk logic never reads client
> sub-positions, so the reduce-only budget on an instrument is your aggregate
> member net there, summed over every `clientUid` and every hedge bucket. The
> `clientUid` on the order does not narrow that budget and is not consulted.
>
> **The consequence for a broker.** An end client closing its OWN position is
> refused with code `2008` (reduce-only violation) whenever your member book
> leans the other way, even though that client genuinely holds the position it
> is trying to close. The sharpest case is a perfectly netted member book: with
> one client long 10 and another short 10 your member net is exactly zero, so
> there is nothing to reduce, and EVERY reduce-only order on that instrument is
> refused, from either client, on either side.
>
> **What the flag is for.** It is a venue-enforced guarantee that an order can
> only ever SHRINK your own house exposure, for example a risk desk flattening
> the member book during a margin call. It is a member risk control, not a
> client close tool.
>
> **To close an end client's position, send a PLAIN order** (no `reduceOnly`)
> and gate it on that client's own position, which you read from
> `GET /v1/positions/end-clients` (section 7). Only you know your client-level
> book; the venue never risk-manages it. This is also exactly why
> `POST /v1/lots/{lotId}/close` defaults `reduceOnly` to false (section 6e).

### Hedge mode: parallel long and short sub-positions (`positionSide`)

By default the venue keeps ONE netted sub-position per (`clientUid`, instrument):
opposite flow for the same client nets, so a client cannot be long and short the
same perpetual at once. Hedge mode lifts that, per order, without a second
`clientUid`.

**The bucket model.** Tagging an order `positionSide: "LONG"` or `"SHORT"` routes
its fills to that BUCKET of the client's sub-position. A client can hold at most
three sub-positions per instrument: one `LONG` bucket, one `SHORT` bucket and the
netting bucket every untagged order acts on. Netting still applies INSIDE a
bucket: a BUY tagged `SHORT` reduces the short bucket (and extends through zero,
flipping inside the bucket, exactly as netting always did), a SELL tagged `SHORT`
extends it. Tagged and untagged flow never touch each other's buckets.

**Worked example.** Your client `HEDGE-1` buys 0.5 `BTC-USD-PERP` tagged `LONG`,
then sells 0.3 tagged `SHORT`. The venue now holds two sub-positions for the
client, `LONG +0.5` and `SHORT -0.3`, and your member position moved by the net
+0.2, exactly as it would have moved without the field. A later BUY of 0.1 tagged
`SHORT` reduces the short bucket to -0.2. An order without `positionSide` keeps
acting on the client's netting sub-position, which the tagged flow never touched.

**Margin is completely unaffected.** Margin stays member-level only (section 7):
your member position is the net sum over all buckets of all clients, and risk
never reads sub-positions. Hedged client flow therefore nets at member level for
margin exactly as it always did; the buckets change reporting and per-client
accounting, never your margin requirement. Funding attribution, liquidation
allocation and final settlement operate per sub-position exactly as they always
did per client: each bucket receives its own funding share, a venue-initiated
close names the bucket it closed, and final settlement closes every bucket.

**Reads.** `GET /v1/positions/end-clients` (and the WS `clientPosition` channel
and `get.clientPositions` op) report each bucket as its own row: rows carry
`positionSide` (`"LONG"` | `"SHORT"`), omitted on netting rows, so a client
using hedge mode can show up to three rows per instrument.

**Composition.** `reduceOnly` composes with `positionSide`: the reduce-only
budget stays member-level (the order can never increase your MEMBER position),
while its fills act on the tagged bucket. STP is unaffected: two orders with the
same `clientUid` trigger your STP policy regardless of their buckets.

### Self-trade prevention (STP) and end-client crossing

Since members execute orders **on behalf of end clients**, two of your orders with
**different `clientUid` values may match each other**, this is a legitimate cross
between two distinct end clients on the venue and executes normally (both sides pay
their applicable fees). STP applies when both orders carry the **same** `clientUid`
(or the same house account): the venue then enforces your configured STP policy
(`CANCEL_RESTING` | `CANCEL_TAKER` | `CANCEL_BOTH` | `REJECT_TAKER`). If your firm
prefers strict member-wide prevention (no same-member matching at all, regardless of
`clientUid`), elect the `MEMBER` STP scope. Since revision 3.62 the policy and the
scope are YOUR OWN election: `PUT /v1/account/stp` `{ "stp": ..., "stpScope": ... }`
(TRADE credential, either field alone keeps the other) sets them at once, the pair
in force reads back on `GET /v1/account` (`stp`, `stpScope`) and on
`GET /v1/account/parameters`, and venue operations may still set them at
onboarding. Since revision 3.63 the same route takes an optional `symbol`: with
it the election is your OWN policy and scope for that market alone (it wins over
the account election there, and `GET /v1/account/parameters` reports it on the
market's row with `stpSource` `SELF_MARKET`); the value `INHERIT` removes the
per-market election so the account policy applies on that market again. Self-trade
prevention is a facility the venue offers uniformly within
its Rules: the four policies and two scopes are the whole menu, and orders of the
same end client never match under any of them.

### Volatility safeguards

Each instrument has a venue-configured **circuit breaker**: if a trade would print
outside the allowed band versus a reference price from the recent trading window,
the instrument is halted for a short cool-down and resting orders remain in place.
During the halt, order entry returns code `3003`; trading resumes automatically and
the resumption is visible on `GET /v1/status` and the WS `status.{symbol}` channel.
The preferred surface is the `venueEvents` channel (section 10.9), whose
`STOP_TRADING` / `RESUME_TRADING` messages carry a typed reason
(`CIRCUIT_BREAKER` / `CIRCUIT_BREAKER_EXPIRY`), the trigger and reference prices,
and the expected resume time.

The breaker is armed in every state in which a market can print, not only while it is
fully trading. A market in `REDUCE_ONLY` or `POST_ONLY` still prints (two members
closing opposite positions cross, and a conditional order placed before the session
changed can still fill), and the same band applies there.

Three consequences worth building for.

**Conditional orders do not trigger while a market is halted**, and that includes a
halt raised by the very run of trades that would have triggered them: if a cascade of
stops trips the breaker, the stops still pending at that instant stay
`PENDING_TRIGGER` instead of activating into the halted market, and they are
evaluated again on the first qualifying price after trading resumes, which can be
materially different from the price that would have triggered them.

**Venue-initiated closes stop at the halt as well.** A liquidation being worked
through the book stops when the market halts, including when it is the liquidation's
own trade that trips the breaker, and the rest of it is worked on the first step
after trading resumes. Nothing else about the position changes at that moment: the
part that was not closed is still yours, still margined and still inside the open
liquidation, and the venue does not trade it into a market it has just told you is
halted.

There is one exception, and it exists so that a suspension cannot leave the venue
carrying a position nobody is collateralising. A breaker halt raised out of a
`REDUCE_ONLY` or `POST_ONLY` session has no resume time and ends only when venue
operations end it. If the member being liquidated has no other market left to work
and its equity has already reached zero, the venue closes that position by auto
deleveraging at the bankruptcy price on the suspended market, rather than waiting.
That is the same last-resort step section 7 describes, applied one market earlier
than usual. It reaches you exactly as any other `ADL` does, on the `venueEvents`
channel and in your executions. Nothing else trades there: the book stays closed,
your own orders are still rejected, and the halt still ends only when venue
operations end it. Every other suspension keeps blocking venue-initiated closes
outright, including a halt raised by venue operations and a breaker halt that
carries a resume time, because both of those end at a moment the venue already
knows.

**Only an automatic breaker halt with a published resume time ends by itself**, and
it is the only halt that carries `expectedResumeTs` and resumes with reason
`CIRCUIT_BREAKER_EXPIRY`. Every other halt stays in force until venue operations lift
it. That covers three cases: a halt raised by venue operations; an operations resume
of a breaker halt, which is final rather than a pause in the published cool-down; and
a breaker halt raised while the market was in `REDUCE_ONLY` or `POST_ONLY`, which
carries no `expectedResumeTs` because ending it automatically would put the market
back into full trading rather than back into the session it interrupted. If venue
operations take over a halt the breaker had raised, the venue republishes
`STOP_TRADING` for that market with reason `ADMINISTRATIVE` and
`expectedResumeTs: null`, superseding whatever it had published for that halt
(section 10.9); the venue never leaves a resume time standing that it will not keep,
on `venueEvents` or on `GET /v1/status`.

A breaker halt without a resume time still reports `reason: "CIRCUIT_BREAKER"` for
as long as it is in force, on `venueEvents` and on `GET /v1/status` alike, including
across a venue restart. Read `reason` and `expectedResumeTs` independently: the
reason tells you who stopped the market, the resume time tells you whether it ends
by itself, and `ADMINISTRATIVE` always means venue operations.

### Venue-initiated closes and reference fields

Execution payloads (REST `GET /v1/executions`, synchronous fills, the WS
`execution` channel) carry a `refSource` field telling you who authored the order
reference on your side of the trade:

- `"refSource": "MEMBER"`: `clOrdId` is your own reference, echoed back.
- `"refSource": "VENUE"`: the venue closed the position and no member order exists,
  so the venue publishes a stable synthetic reference in the reserved `KX-`
  namespace: `KX-LQ-{orderId}` for a liquidation slice, `KX-ADL-{tradeId}` for an
  ADL close. Both are pure functions of durable identifiers, so the same string
  appears on every surface (live WebSocket, REST re-reads, history). Because
  member `clOrdId`s can never start with `KX-` (code `2020`), the two namespaces
  cannot collide.

Since revision 3.70 the same execution payloads (the synchronous order answer's
`executions`, `GET /v1/executions`, the WS `execution` frame) and the RFQ accept
answer carry `uti`, the EMIR unique transaction identifier of the VENUE leg: the
venue's LEI followed by the `tradeId` zero-padded to 12 digits (32 characters of
A-Z 0-9), on PERP and BARRIER executions only (a barrier entry and an early
close included, a venue-authored liquidation or ADL close too); a SPOT fill and a
yield contract take never carry it. It is derived at read time from the durable
trade id, so the same fill reads the same `uti` on every surface and on every
later read, and it is OMITTED (never null) until the venue has configured its LEI.
The stream backfill (`GET /v1/stream/events`, section 11) derives it at read time
too, so a backfilled `execution` row's `data` stays byte-identical to the live
frame's whenever the venue LEI was configured at both times. FIX carries no such
tag.

Venue-generated orders never produce an accept event: a liquidation slice is first
visible as a fill or a cancel. `clientUid` on a venue-initiated close is the end
client whose sub-position was closed, and the tuple (`clientUid`, `symbol`)
resolves the close to exactly one end-client sub-position in your books. You are
never shown another member's end-client identifiers.

`clientUid` also rides every order rejection, on the synchronous response and on
the WS `order` channel notification alike, so rejected flow reconciles by either
your `clOrdId` or your reporting identifier.

`GET /v1/instruments` publishes `maxLeverage` (exact decimal string) and
`leverageMode` (`FIXED` | `TIERED`) per instrument. Read these instead of
hard-coding a `leverage` assertion: sandbox and production instrument
configurations differ.

**Leverage responsibility, stated plainly.** The venue margins your MEMBER account
on the instrument tier schedule; it does not model, check or enforce a leverage
limit per end client. If you offer your clients a fixed leverage (for example flat
10x), enforce it on your side before submitting the order. The optional order-level
`leverage` field is an ASSERTION for detection, not a control: the venue validates
it against your account's effective margin and rejects a false assertion with 2018,
but it never changes the margin applied. The assertion is member-aware: if the
venue runs your account at a stricter margin profile (visible as `imMultiplier` on
`GET /v1/account`, `"1"` unless applied as a published class or a Rules measure), your effective leverage is
`maxLeverage / imMultiplier` and that is the value the assertion validates against.

## 6a. Spot markets

Spot markets (`"kind": "SPOT"` on `GET /v1/instruments`) trade the base asset
against the settlement currency with **real delivery** and **full prefunding**,
no leverage, no short selling, no funding, no liquidation of spot balances. The
order model, book, market data, STP, and Market Segments behave exactly as on
perps.

**Current spot listings** (revision 3.50, sandbox; informative, ALWAYS read
`GET /v1/instruments`). Each market is a central limit order book (`"mode":
"ORDER_BOOK"`) on the plain pair symbol, one book per base asset, USD-quoted and
USD-settled, sized exactly like the perpetual of the same underlying:

| Market | Base asset | Price tick | `qtyStep` = `minQty` | `minNotional` | `maxOrderNotional` | Fees maker / taker |
|---|---|---|---|---|---|---|
| BTC-USD | BTC | 0.5 | 0.0001 | 5 | 10000000 | 0.0002 / 0.0005 |
| ETH-USD | ETH | 0.05 | 0.001 | 5 | 10000000 | 0.0002 / 0.0005 |
| SOL-USD | SOL | 0.01 | 0.01 | 5 | 10000000 | 0.0002 / 0.0005 |
| BNB-USD | BNB | 0.05 | 0.001 | 5 | 10000000 | 0.0002 / 0.0005 |
| XRP-USD | XRP | 0.0001 | 1 | 5 | 10000000 | 0.0002 / 0.0005 |

The base asset codes (`BTC`, `ETH`, `SOL`, `BNB`, `XRP`) are the `asset` values
of the `assets` array on `GET /v1/account` and the `ccy` of the asset legs on
the private `wallet` channel; every asset balance is a decimal string with up
to six decimal places. A `EUR` asset row (a deposited euro balance on a
USD-settled venue) additionally carries the informational `reference` block of
revision 3.70, the row's `total` times the ECB euro reference rate as a
non-binding USD figure (see the `GET /v1/account` row in section 9); the block is
omitted on every other asset row and while no rate from the last five calendar
days is held, and it never converts anything. Marks for spot follow the same reference line as the
perpetual of the same underlying (index = mark on spot); the `funding` block
and the barrier economics pair are absent on spot rows. Spot markets are
offered under the venue's MiCA notification: spot fills are not MiFIR
transactions, so no RTS 24 order record or RTS 22 transaction report is
produced for them, and the section 6d missing-identity report lists only
`clientUid`s that traded on the MiFIR-scope markets (perpetual and barrier
markets, and the swap markets from the date venue operations announce under
revision 3.70, section 6h). Differences from perps:

- **Sizing.** Spot trades on the perpetual's fractional grid (`qtyStep`
  `1/unitScale`, section 2a; revision 3.50, earlier revisions said whole units),
  so `price × qty` is your exact quote-cash leg and `qty` your exact asset leg,
  reconcile with exact math, no rounding surprises: a fill of `0.0003` BTC at
  `70000` moves exactly `0.0003` BTC and `21` USD. Fees are charged in the
  settlement currency: `fee = notional × rate`, rounded half-even per fill.
- **Buys** reserve worst-case quote cash at order entry (limit price, or the
  protection-collar bound for market orders, plus the fee at your worst applicable
  rate). Insufficient free cash rejects with `3001`. Reserved cash is shown as
  `spotReserved` on `GET /v1/account` and is excluded from `available` and from
  withdrawals.
- **Sells** reserve the base asset: free asset balance (see `assets` on
  `GET /v1/account`) must cover the remaining quantity. Insufficient asset rejects
  with `3004`.
- **Settlement is per fill and atomic**: buyer pays notional + fee and receives the
  asset; seller delivers the asset and receives notional - fee. Asset movements
  appear on the private `wallet` WS channel with the asset code in `ccy`.
- **Unsupported on spot** (code `2016`): `reduceOnly`, `positionSide` (hedge
  mode is a perpetual feature, section 6), and `STOP_MARKET` /
  `TAKE_PROFIT_MARKET` **buy** orders (a market buy triggered later has no
  computable worst-case cost to prefund). Stop-limit / take-profit-limit buys and
  all conditional sells are supported.
- **Funded-at-fill rule.** Reservations are re-checked at execution time: if events
  since acceptance (e.g. losses on your perp positions in the shared cash account)
  have made a resting spot order unaffordable, it is **canceled**
  (`cancelReason: "SPOT_UNFUNDED"`) instead of settling into a negative balance.
- **Reduce-only sessions** (volatility/staleness): spot **sells** remain allowed
  (they only raise cash), spot buys are rejected with `3002`.
- Perp-only fields are zeroed on spot tickers (`fundingRate`, `fundingRatePct`,
  `predictedFundingRate`, `predictedFundingRatePct`, `nextFundingTs`,
  `openInterest`), with the single exception of `fundingRateAnnualizedPct`, which
  is OMITTED rather than `0` so a spot market is never presented as a funded one
  that happens to be free; `GET /v1/positions` never contains spot rows, your
  holdings live in `assets` on the account.
- **One cash pool.** Spot quote reservations and perp margin draw on the same
  settlement-currency account: cash reserved for spot buys is unavailable as perp
  margin and vice versa. Spot **asset** holdings are NOT collateral for perp
  positions (no cross-collateralization), and members remain responsible for
  client-level asset records (the venue records `clientUid` on every execution).

**Response** is the synchronous outcome, final status plus any executions produced
while matching (`NEW`, `PARTIALLY_FILLED`, `FILLED`, `CANCELED`, `REJECTED` with
`rejectCode`). The order is durably journaled before you receive the response.

**Where a rejection lands, per transport.** The three transports report the same
verdict in different places, deliberately: each protocol has its own place for a
verdict and the venue uses that place rather than inventing another. The
convention is stable and is not a divergence to be aligned; a client library that
maps all three onto one internal outcome, the code and the order state, is the
intended integration.

- REST: an ENGINE rejection (price band, margin, post-only would cross,
  reduce-only, self-trade, minimum notional and the like) answers HTTP 200 with
  envelope `code` 0 and the outcome in the payload, `data.status` `REJECTED` and
  `data.rejectCode`. The envelope reports that the request was accepted,
  sequenced and durably journaled, which it was; the payload is the order's
  state, in exactly the shape `GET /v1/orders/{orderId}` and the history read
  serve it, so one parser handles the ack and every later read. Read the response
  as two gates in sequence: `code == 0`, then `data.status != "REJECTED"`. A
  GATEWAY rejection (shape, grid, unknown symbol, authentication, throttle, the
  reserved namespace) never reached the engine and is envelope-level: HTTP 4xx,
  the code in `code`, `data.clOrdId` echoing your reference, no order payload.
- WebSocket: the op ack IS the verdict. Both kinds of rejection ack
  `success: false` with the code and the registry wording in `msg`; an engine
  rejection additionally carries the same order-state payload in `data`
  (`status` `REJECTED`, `rejectCode`), a gateway rejection `data.clOrdId`
  (section 10.5).
- FIX: an ExecutionReport. A gateway rejection is one immediate report with
  `150=8`, `39=8`, `OrderID(37)=0` and the code in `9010`; an engine rejection
  arrives through the post-durable report chain as the order's terminal report,
  `150=8`, `39=8` and the code in `9010` (an order the engine accepted and then
  rejected inside the same command, a post-only order that would cross for
  instance, first receives its `150=0` accept), FIX rules of engagement
  section 4.

**Queue position on the ack.** When a placement RESTS an order on the book, the
ack carries two additional fields: `queueQtyAhead`, a decimal quantity string,
the quantity resting ahead of your order at its price level (ahead in FIFO
priority), and `queueOrdersAhead`, an integer, the number of orders ahead of it
at that level. The fields are PRESENT exactly when the placement left the order
resting: a GTC or GTD limit that rested in full, the resting remainder of a
partial fill, a post-only rest. A first order at an empty level reports
`"0"` and `0`, present, meaning front of the queue. The fields are ABSENT, not
null, wherever no queue position exists: full fills, the canceled remainder of
an IOC, FOK or market order, conditional orders accepted as `PENDING_TRIGGER`,
rejections, amend responses, and order-history reads. Presence means resting
AT ACK TIME: when the same command that placed the order also terminates it
after the rest, for example a stop released by the placement's own prints
crosses into the just-rested remainder and fills it, the ack reports the
terminal status and the queue fields are cleared, so their presence on the
ack always implies the order is still working. The `ORDER_ACCEPTED` event on
the `order` channel and the FIX accept report keep the figures as a record
of the moment of rest. The figures are a
snapshot taken at the moment the order rested, measured after any reduce-only
trim the same placement caused, and they are immediately stale: later cancels,
fills and amends by other members move the queue and the venue never updates
them on the order. For a live picture, watch the book channels. The same two
fields ride the WS `order.place` ack and the `ORDER_ACCEPTED` frame on the
private `order` channel, and on FIX the accept ExecutionReport carries them as
venue tags `9017` and `9018` (see the FIX rules of engagement).

### Manage orders

| Endpoint | Description |
|---|---|
| `POST /v1/orders/batch` | Up to 50 complete, independent orders in one message, each with its own `clOrdId` and `clientUid`; per-entry results in array order (section 6c) |
| `POST /v1/orders/batch/amend` | Up to 50 amendments of your own orders in one message, each entry `{orderId, price?, qty?}` processed by the matching engine exactly as `PUT /v1/orders/{orderId}`; one order-budget unit per entry; per-entry results in array order (section 6c, revision 3.74) |
| `POST /v1/orders/batch/cancel` | Up to 50 cancellations of your own orders in one message, each entry `{orderId}` or `{clOrdId}`; free and never throttled; per-entry results in array order (section 6c, revision 3.74) |
| `DELETE /v1/orders/{orderId}` | Cancel one order. Answers the cancelled order, rendered from the cancel event: `status` `CANCELED`, `cancelReason` `USER_REQUESTED`, `acceptedTs` the cancel's time (revision 3.83 states it; the answer is unchanged). An empty `orderId`, `DELETE /v1/orders/` with a trailing slash (what a path built as `/v1/orders/{orderId}` becomes when the id is empty), answers HTTP 400 `5003` naming it and cancels nothing, with or without a query (revision 3.84; before, that path was the cancel-all of `DELETE /v1/orders`). Since revision 3.86 a cancellation of a `GTD` order at or past its `expireTs` answers `2014`, the answer it receives once the venue's expiry sweep has run, and the order is reported `EXPIRED` (from the matching engine's replay-contract line for this revision, section 6 `tif`) |
| `DELETE /v1/orders?clOrdId=` | Cancel by your `clOrdId`, sent alone (with `symbol` or `side` beside it the request is refused `5003`, revision 3.84) |
| `DELETE /v1/orders?symbol=&side=` | Cancel all, optionally per `symbol` and/or `side` (`BUY` or `SELL`, in any case); without a filter it cancels every open order you have on every market, untriggered conditionals included. Only `/v1/orders` without a trailing slash is this route: `/v1/orders/` is `DELETE /v1/orders/{orderId}` with an empty `orderId`, refused `5003` since revision 3.84. Since revision 3.84 the route reads the query parameters `clOrdId`, `symbol` and `side` and nothing else, their names matched without regard to case: a parameter it does not define, a parameter given twice, a `side` other than `BUY` or `SELL` (an empty one included) or `clOrdId` with another filter answers HTTP 400 `5003` with a `msg` naming it, a request body answers `2017`, and nothing is cancelled (before, such a request without `clOrdId` ran as a cancel of every open order the remaining filters matched, a repeated `symbol` excepted, which answered `2001`, and one with `clOrdId` cancelled at most the order `clOrdId` named; the migration table below) |
| `PUT /v1/orders/{orderId}` | Amend `price` and/or `qty` (`qty` is the new TOTAL quantity, fills included). Quantity *decrease* at the same price keeps queue priority; any price change or quantity increase is processed as cancel+new (fresh priority, re-matched immediately). Margin, band, and position limits are re-checked. Outside a call auction, the re-match of an amend that reprices the order to cross the book treats it as a new order: a post-only order is rejected `2005` and removed, exactly like a post-only order that would cross at entry; an order that meets your own resting order with the same `clientUid` follows your self-trade policy, which under `REJECT_TAKER` rejects the amended order `2015` (or cancels it `STP_CANCEL` when it was part filled), under `CANCEL_TAKER` and `CANCEL_BOTH` cancels it `STP_CANCEL`, and under the default `CANCEL_RESTING` cancels your resting order instead; your own order with a different `clientUid` trades under the default end-client scope. These outcomes answer inside HTTP 200 with the order's `status` `REJECTED` or `CANCELED` (revision 3.74 corrects this row, which said nothing of them). During a call auction, or while the market is closed, the repriced order rests for the uncross. Every other refusal of an amend leaves the order untouched, except one: since revision 3.86 an amendment of a `GTD` order at or past its `expireTs` answers `2014`, the answer it receives once the venue's expiry sweep has run, and the order is reported `EXPIRED`. The answer is the order read just after the amendment (the open order, else its terminal record), so it can already carry a later fill, while the envelope `asOfSeq` is the amendment's own sequence number (revision 3.83 states it; the answer is unchanged) |
| `GET /v1/orders/open?symbol=` | Open orders (including untriggered conditionals). `status` is the live state: `NEW` while nothing has filled, `PARTIALLY_FILLED` once any of the order has, whether it part filled on entry or its resting remainder was later consumed by an aggressor (revision 3.60; before it the latter case read `NEW` with `remainingQty` below `qty`, and the sandbox keeps reading so until venue operations announce the activation of that rule) |
| `GET /v1/orders/{orderId}` | Single order state, same `status` semantics as the open-orders read |
| `GET /v1/orders/history?symbol=&from=&to=&limit=` | Closed orders AND rejections (the only place a rejection that never reached the book can be re-read), newest first, served from the projection store (envelope `asOfSeq` is the store watermark; `limit` default 100, max 1000; `from` and `to` bound `acceptedTs`, below). Rows carry the optional `positionSide` (`LONG` \| `SHORT`), omitted, not null, on netting orders, exactly like the live order surfaces (hedge mode, this section). While the store has a recorded hole (`store.status` `REPAIRING`, section 11.3) the rejection rows stop at the watermark, while the closed-order rows are not held: they can include orders closed after the hole, above `asOfSeq`, while orders closed inside it are missing, so read them as complete only once the status is `COMPLETE` again |
| `GET /v1/executions?symbol=&limit=` | Your recent executions with fees, roles, flags, `clientUid` and, since revision 3.70 on PERP and BARRIER rows once the venue LEI is configured, the venue-leg `uti` (section 6.4). Served from the projection store (envelope `asOfSeq` is the store watermark); since revision 3.83, while the store has a recorded hole (`store.status` `REPAIRING`, section 11.3), it serves only the executions at or below the watermark, as `GET /v1/account/ledger` does |

**`acceptedTs` per surface (revision 3.83, documentation only: nothing
changed).** The field names a different instant on different surfaces:

- **Order frames and everything rendered from an order event.** On every
  frame of the `order` channel and on its rows of the event stream
  (`GET /v1/stream/events`), `acceptedTs` is the venue's sequencing time of
  the event the frame reports: the acceptance on `OrderAccepted`, the
  amendment on `OrderAmended` (a venue reduce-only trim included, which carries
  the time of the trade that shrank your position), the cancel on
  `OrderCanceled`, and the trigger, the completing fill, the expiry or the
  rejection on the other frames. Order frames carry no other timestamp; on FIX,
  `TransactTime(60)` of each ExecutionReport is the same instant. The answers
  rendered from an order event carry the event time too: the
  `DELETE /v1/orders/{orderId}` answer, the rows of a cancellation batch, a
  `PUT /v1/orders/{orderId}` answer when the amendment ended the order, and
  `GET /v1/orders/{orderId}` or `get.order` for an order that is already
  terminal.
- **Open-order reads.** On `GET /v1/orders/open`, `get.openOrders`, and
  `GET /v1/orders/{orderId}`, `get.order` and the `PUT` answer while the order
  is open, `acceptedTs` is the placement time. It moves to an amendment's time
  only when an amendment of a working order changes the price or increases the
  quantity (processed as cancel and new); a size-down at the same price, a
  venue trim and any amendment of an untriggered conditional order keep it, and
  so does a conditional order that triggers, although it joins its price level
  only then.
- **Order history.** On `GET /v1/orders/history`, a `FILLED`, `CANCELED` or
  `EXPIRED` row carries the first acceptance in `acceptedTs` and the time of
  the terminal event in `closedTs`; a `REJECTED` row carries the rejection time
  in both, also for an order that rested and was rejected when an amendment
  repriced it to cross. The `from` and `to` parameters bound `acceptedTs`.

To track the open-read value from the order channel alone, take `acceptedTs`
from `OrderAccepted` and replace it from an `OrderAmended` only when that
frame's `status` is not `PENDING_TRIGGER` and the amendment changed the price
or increased `qty` against your previous state.

**Migration table, revision 3.84** (dated 21 October 2026; the refusals of the
cancel-all are corrections in force at publication by the operator's decisions of
2026-10-20 and, for `DELETE /v1/orders/` with a trailing slash, 2026-10-21, the
venue being before production go-live, the 30-day notice of the versioning policy
waived for them; the `side` filter of the WebSocket `order.cancelAll`, decided on
2026-10-21, is additive; this table is the guide of what to do). The
cancel-all refuses a filter it cannot read instead of running without it, so a
refused request cancels nothing. There is no `clientUid` filter: the matching
engine's cancel-all takes no end-client predicate and the venue offers no FIX
mass cancel to carry one, so cancel one end client's orders by `orderId`
(`POST /v1/orders/batch/cancel`, the ids and their `clientUid` from
`GET /v1/orders/open`).

| What you send | Before revision 3.84 | From revision 3.84 | What to do |
|---|---|---|---|
| `DELETE /v1/orders` with a query parameter other than `clOrdId`, `symbol` and `side` (`?clientUid=`, `?orderId=`, a misspelt `?symbl=`, a cache-busting `?_=1`) | the parameter ignored: the request cancelled every open order the other filters matched, and without one every open order you had on every market, every end client's untriggered conditionals included | HTTP 400, `5003` naming the parameter: "unknown parameter 'clientUid': DELETE /v1/orders reads clOrdId, symbol and side only; nothing was cancelled" | send only the three parameters; cancel one order by `DELETE /v1/orders/{orderId}` or `?clOrdId=`, one end client's orders by `orderId` |
| `DELETE /v1/orders` with a `side` other than `BUY` or `SELL` (`?side=BID`, an empty `?side=`) | no side filter: both sides cancelled | `5003` "side must be BUY or SELL; nothing was cancelled" | send `BUY` or `SELL` in any case, or no `side` |
| a parameter given twice (`?side=BUY&side=SELL`, `?clOrdId=a&clOrdId=b`, a name repeated in another case) | the values joined with a comma: two sides read as no side and cancelled both, two symbols answered `2001` and two references `2014` | `5003` naming the parameter: "side is given more than once; nothing was cancelled" | send each parameter once |
| `clOrdId` with `symbol` or `side` | `symbol` and `side` ignored, the order named by `clOrdId` cancelled | `5003` "clOrdId names one order and cannot be combined with symbol or side; nothing was cancelled" | send `clOrdId` alone |
| a request body on `DELETE /v1/orders` (`{"clOrdId": ...}`, `{"orderId": ...}`, any other text, chunked or not) | the body ignored: the request ran on its query alone, without one as a cancel of every open order | `2017` "DELETE /v1/orders takes no request body: send clOrdId, symbol and side as query parameters; nothing was cancelled" | send no body, or `Content-Length: 0` |
| `DELETE /v1/orders/` with a trailing slash, with or without a query (`/v1/orders/?symbol=BTC-USD-PERP`; what a path built as `/v1/orders/{orderId}` becomes when the id is empty) | served as `DELETE /v1/orders`: the request cancelled every open order the query matched (with `clOrdId`, at most that order), without one every open order you had on every market, untriggered conditionals included | HTTP 400, `5003` naming the empty `orderId`: "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" | check the id before you build the path; send `DELETE /v1/orders`, without the slash, when you mean the cancel-all |
| WebSocket `order.cancelAll` whose args object carries a member other than `symbol` and `side` (`clientUid`, a batch body `{"cancels": [...]}` meant for `orders.batchCancel`, `Symbol` or `Side` in another case) | the member ignored: every open order the `symbol` matched cancelled, without it every open order | `2017` naming the member: "unknown field 'clientUid': order.cancelAll reads symbol and side only; nothing was cancelled" | send only `symbol` and `side`; a cancellation batch goes to `orders.batchCancel` |
| `order.cancelAll` with `symbol` or `side` `null`, of another JSON type or given twice, or whose args are given twice or are not exactly one object (no args, `null`, an object, an empty array, two objects) | a `null` `symbol` read as absent, a `side` of any shape ignored and a second args object ignored and of two `args` keys the last read, so each cancelled every open order the rest matched; a `symbol` of another type and args that were absent, not an array, an empty array or an array whose first element was not an object `2013` "malformed request" | `2013` ("symbol must be a string; nothing was cancelled", "symbol is given more than once; nothing was cancelled", "order.cancelAll takes exactly one args object; nothing was cancelled", "args is given more than once; nothing was cancelled", and the same sentences for `side`) | send `symbol` and `side` as strings, once each, in one args object; `[{}]` without a filter |
| `order.cancelAll` with `side` | not read: both sides cancelled | read as on `DELETE /v1/orders?side=` (additive, the operator's decision of 2026-10-21): `BUY` or `SELL` in any case cancels that side only, with `symbol` that side on that market; another string, an empty one included, `5003` "side must be BUY or SELL; nothing was cancelled" | send `BUY` or `SELL` to cancel one side, no `side` to cancel both |

**Migration table, revision 3.86** (dated 21 October 2026; the rows below where the
two columns differ are corrections published at this revision by the operator's decision
of 2026-10-20, the venue being before production go-live, the 30-day notice of the
versioning policy waived for them; the matching engine applies them from its
replay-contract line for this release, which on a venue started on the
production contract is its first command, and venue operations announce the date on
any environment where that line lies ahead; this table is the guide of what to do):

| What you do | Before revision 3.86 | From revision 3.86 | What to do |
|---|---|---|---|
| rest a `GTD` order and let its `expireTs` pass | it could still trade until the venue's expiry sweep reached it, up to about a second after `expireTs` (longer while the venue's timers lagged), and an order that crossed it then filled against it | it never trades at or after `expireTs`, measured against the time the venue sequences each command: an incoming order or a call auction's uncross that reaches it expires it (status `EXPIRED`) and goes on to the next order in the book | keep reading the `EXPIRED` report as the order's end; never hold a fill timestamped at or after `expireTs` against your `GTD` order as possible |
| use `GTD` as a dead-man's switch on your quotes | the quote stayed executable for up to about a second after `expireTs` | the quote is not executable from `expireTs` on, whether or not the `EXPIRED` report has reached you | expect the `EXPIRED` report up to about a second after `expireTs` when nothing touches the order (the sweep's cadence is unchanged) |
| place a conditional `GTD` order whose trigger price is reached after its `expireTs` | it triggered and its child could trade | it expires instead (status `EXPIRED`, no trigger report) | treat the expiry as the order's end |
| amend a `GTD` order after its `expireTs` | the amendment was accepted while the sweep had not reached the order, and a re-priced order could trade | `2014` (order not found), the answer once the sweep has run, and the order is reported `EXPIRED` | read `2014` on an amendment as "the order has ended" and look it up for its final state |
| cancel a `GTD` order after its `expireTs` that the sweep has not reached | canceled (`USER_REQUESTED`) | `2014` (order not found), the answer once the sweep has run, and the order is reported `EXPIRED` (`GTD_EXPIRED`); a cancel-all reports it `EXPIRED` and counts it in `canceled`; nothing traded either way | read `2014` on a cancellation as "the order has ended" and look it up for its final state |

## 6b. RFQ trading (private)

Markets whose `mode` is `RFQ` on `GET /v1/instruments` trade by **request for
quote** instead of a central limit order book. An RFQ market has no book: order
entry there is rejected with code `2022`, and RFQ operations on an order-book
market are rejected with `2023`. All five kinds trade this way: `PERP` (an
off-book perpetual trade, margined and netted exactly like a book fill), `SPOT`
(prefunded delivery at the quoted price), `SECURITY` (prefunded delivery
exactly like `SPOT`, section 6i), `BARRIER` (knock-out contracts, below) and
`SWAP` (yield contracts, section 6h, where the same verbs run the other way
round: the writer posts a standing offer, any other member takes a size of it,
and quotes do not exist).

**Flow.** The requester opens an RFQ; other members quote it; the requester
accepts at most one quote. Negotiation is anonymous in both directions: quoting
members never learn who requested, and the requester sees price, size and
validity but never a quoting member's identity. Market Segments apply
throughout: a member outside your mutual acceptance set never sees, quotes or
trades your RFQ. Discovery is polled REST (the tables below); lifecycle events
push on the private WS `rfq` and `barriers` channels (section 10.3).

| Endpoint | Description |
|---|---|
| `POST /v1/rfq` | Open a request (idempotent on `clOrdId` from the matching engine's replay-contract line of revision 3.85, NOT idempotent before it, see below): `symbol`, `side`, `qty`, `clOrdId`, `clientUid`, optional `reg`, optional `expiresInMs` quoting window (default 60000; minimum 1 s, maximum 24 h), and on barrier markets a `barrier` object (`level`, optional `expiryTs`, optional `takeProfit`, optional `strictTakeProfit` (revision 3.45, see "Strict take profit" below)); on a market that publishes `maxContractLifeHours` (revision 3.70) `barrier.expiryTs` is required, at most that many hours after the request (`2027` otherwise). `qty` is a decimal number of units on the market's `qtyStep` (the perpetual's step on the barrier markets since revision 3.71; `2002` off the grid) and obeys the order-size cap (2013): `maxOrderNotional` valued at the current mark where published, else the base-unit `maxOrderQty` (section 2a), and the minimum notional (2028): `price x qty` at the current mark must reach the market's `minNotional`, 100 USD on every barrier market since revision 3.71, and every quote is re-checked at its quoted entry price (section 2a). Or a CLOSE REQUEST (revision 3.44): `symbol` and `closeContractId` (an open barrier contract you hold), NO `side`, `qty` or `barrier` (the venue derives them), see "Early close" below. Every RFQ view of a barrier request (this answer, `mine`, `open`, the WS `rfq` `created` frame) carries `strictTakeProfit`, `premiumBps` and `premium` (revision 3.45: the holder's maximum loss, INDICATIVE at the current mark on an open request, the contract's frozen figure on a close request). Since revision 3.58 a barrier OPENING request also accepts optional `levelOrigin` (`CLIENT`, the default, the end client typed the levels \| `DERIVED`, a routing system derived them from a pattern setup) and optional `clientClass` (`RETAIL` \| `PROFESSIONAL` \| `ELIGIBLE`; only meaningful with a `clientUid`, and a request WITH a `clientUid` and WITHOUT it defaults to RETAIL); any other word is `2013`, and so is either field on a close request or a non-barrier market. Both are frozen onto the contract at accept and shown to the WRITER beside `capacity`/`clientTag`; neither is read by any pricing or risk rule |
| `GET /v1/rfq/open?symbol=` | The open board: every live RFQ you are eligible to quote, requester anonymous, live quote count, your own quote (if any) as `myQuote`. A close request (`closeContractId` set) is on YOUR board only when you wrote that contract. Since revision 3.57 every board row carries `capacity` (`OWN_ACCOUNT` \| `FOR_CLIENT`) and, on `FOR_CLIENT` rows, `clientTag`, your own pseudonymous tag of the requester's end client (see "Writer transparency" below; `clientTag` is null on `OWN_ACCOUNT` rows) |
| `GET /v1/rfq/mine` | Your own open RFQs with their live quotes (price and validity per quote, makers anonymous); a close request of yours carries `closeContractId` |
| `GET /v1/rfq/quotes/mine` | Your live quotes across all open RFQs (`closeContractId` on a quote against a close request) |
| `POST /v1/rfq/{rfqId}/quotes` | Quote an RFQ: `price`, `clOrdId`, `clientUid`, optional `reg`, optional `expiresInMs` validity (default 30000; same bounds). One live quote per maker per RFQ; a re-quote replaces the earlier one (since revision 3.85 under a NEW `clOrdId`: from the matching engine's replay-contract line for this release a reference you already used answers `2009`, see "References" below), and the request's full size counts against your order-size cap (2013): `maxOrderNotional` valued at the quoted entry price where published, else the base-unit `maxOrderQty` (section 2a). On a close request only the contract's writer may quote, at a price strictly inside the contract's terms (`2027`). Since revision 3.58 a quote on a barrier OPENING request may carry `premiumBps`, YOUR OWN premium share for this quote, integer bps of the barrier distance: at least the market's effective share for the request's variant (normal: `10000 - knockOutRebateBps`; strict: `strictTakeProfitPremiumBps`) and at most 10000, else the NEW code `2044` PREMIUM_SHARE (you can only make a contract MORE expensive than the market base, never cheaper); on a close request or a non-barrier market it is `2013`. Omitted = the market share. The requester sees your quote's `premiumBps` (its quote rows and the WS `quoted` frame) before accepting; the accept freezes YOUR share onto the contract exactly where the market share is frozen, and the contract's `premium`/`premiumBps` then read the frozen share everywhere |
| `POST /v1/rfq/{rfqId}/accept` | Requester accepts one quote: `{"quoteId": ...}` plus optional `reg`. Executes immediately at the quoted price; on a barrier market the response also carries the opened `contractId` and echoes the contract's terms (`barrier`, `takeProfit` when set, `contractExpiryTs` when dated) and, since revision 3.45, its `strictTakeProfit`, `premium` (the holder's maximum loss, frozen at the accepted entry) and `premiumBps`. On a close request it CLOSES the contract at the quoted price and the response carries `contractId` (the closed contract), `closeContractId`, `settlePrice` and your `realizedPnl` (plus the closed contract's `strictTakeProfit`, `premium`, `premiumBps`). Since revision 3.70 a PERP accept, a barrier entry and an early close also carry the venue-leg `uti` (section 6.4) once the venue LEI is configured, a swap take never does, and a barrier accept (opening or early close) also carries `isin`, the DSB ISIN of the contract's term set, once the venue has allocated it (null, so omitted, until then and on the sandbox; never present on perp, spot or swap accepts) |
| `POST /v1/rfq/{rfqId}/cancel` | Requester withdraws the RFQ (a close request included). Never rate-limited, like order cancels |
| `POST /v1/rfq/{rfqId}/quotes/{quoteId}/cancel` | Maker withdraws a quote. Never rate-limited |
| `GET /v1/barriers?status=&limit=` | Your barrier contracts, both roles (see below). Default (`status` omitted or `OPEN`): the live contracts, marked to the live mark. `status=CLOSED`: your recently settled contracts (settlement price, role-signed `realizedPnl`, `terminationReason` incl. `EARLY_CLOSE`, `closedTs`; newest first, `limit` default 100, maximum 1000, kept since the last venue start; the durable record is your statement and the ledger). `status=ALL`: both, live rows first. Any other value answers `2013`. Since revision 3.57 your WRITER rows (live and closed) additionally carry the holder side's `capacity` and, on `FOR_CLIENT` contracts, your `clientTag` of the holder's end client (see "Writer transparency" below); holder rows never carry the pair. Since revision 3.70 every row (live and closed, holder and writer alike) carries `isin`, the DSB ISIN of the contract's term set once allocated (null, so omitted, until then and on the sandbox; a closed row shows a later allocation too) |

- **`POST /v1/rfq` is NOT idempotent on `clOrdId` before the matching engine's
  replay-contract line of revision 3.85** (revision 3.83 states it; from that line
  see "References" below). Unlike order entry, RFQ creation, a swap offer
  included (section 6h), echoes `clOrdId` and never deduplicates on it: a resend
  with the same `clOrdId`, identical or not, opens another request and never
  answers `2009`, and order entry does not see an RFQ's reference (an order
  under the same `clOrdId` is a new order). After a timeout, read
  `GET /v1/rfq/mine` (your open requests, each with its `clOrdId`) before you
  send again, and withdraw a duplicate with the free
  `POST /v1/rfq/{rfqId}/cancel`.
- **Rate limits.** Create, quote and accept consume the order-entry bucket,
  exactly like order placement; the `GET` reads consume the read bucket; the two
  cancel routes consume neither.
- **Trader ID.** `reg.traderId` is enforced on create, quote and accept exactly
  as on order entry (code `2021`, staged per member, section 6).
- **References (revision 3.85).** A request (`POST /v1/rfq`: an ordinary request,
  a close request, a yield contract offer) and a quote are idempotent on their
  `clOrdId` like an order (section 2): a `clOrdId` you already used, on an order, a
  swap take, a request or a quote, within the venue's retention of section 2, answers
  `2009` and nothing is opened or replaced; a different `clOrdId` of yours that
  hashes to the same 64-bit value answers `2045`. There is no restatement of the
  original: after a lost answer read `GET /v1/rfq/mine` (requests) or
  `GET /v1/rfq/quotes/mine` (quotes), which show your `clOrdId`, before you send a
  new request or quote under a new reference. Without a `clOrdId` there is no such
  protection (a resend opens a second request); send one on every request and quote.
  A re-quote that should replace your live quote carries a new `clOrdId`. An order
  under a reference a request, a quote or a swap take of yours holds answers `2009`
  as well, never a restatement of an order. Applied
  by the matching engine from its replay-contract line for this release (from the
  first command of a venue started on the production contract; venue operations
  announce the date on any environment where the line lies ahead); before that line a
  reused reference is admitted as before.
- **Validation.** Quotes are refused outside the instrument price band (code
  `2003`). The minimum notional (section 2a; 100 USD on every barrier market
  since revision 3.71, 5 USD elsewhere) applies to RFQs too: a request
  below the instrument's `minNotional`, valued at the current mark, rejects at
  create with `2028`, and every quote is re-checked at its quoted entry price
  (the exact notional an acceptance would trade), again with `2028`. An accept
  needs no further notional check because the accepted quote's price cannot
  change. Quoting your own member's RFQ is rejected with `2026`. Barrier terms
  invalid for the market and side (level on the wrong side of the mark or the
  quoted entry, a `takeProfit` not strictly on the profit side of the mark at
  create or of the quoted entry at quote and accept, expiry in the past, or a
  `barrier` object on a non-barrier market) reject with `2027`, as does a
  STRICT take-profit request (revision 3.45) without a `takeProfit`, without an
  `expiryTs`, with an `expiryTs` more than 24 hours after the request, or on a
  market whose `strictTakeProfitPremiumBps` is 0 (re-checked at accept), or, on
  a market that publishes `maxContractLifeHours` (revision 3.70), an omitted
  `expiryTs` or one more than that many hours after the request, and a
  quote whose implied maximum loss on either side, the entry-to-barrier
  distance (or, when set, the entry-to-take-profit distance) times `qty` times
  the contract value, would exceed the venue's exact-money range
  (`1000000000000000000` settlement-currency units; the same absolute ceiling
  as section 2a, applied to the contract the acceptance would open, and equally
  unreachable at any tradable size). A `takeProfit` off the tick grid rejects
  `2002` like any price. An unknown, closed or expired RFQ answers `2024`; an
  unknown, withdrawn or expired quote answers `2025`. On a CLOSE REQUEST
  (below): a `closeContractId` that is not an open barrier contract you hold on
  `symbol`, or one that already has a close request open, rejects `2035`;
  `side`, `qty` or `barrier` sent alongside it reject `2013`; a quote by any
  member but the contract's writer is answered `2024`, and a writer's price not
  strictly inside the contract's terms rejects `2027`.
- **Barrier accept guard (revision 3.55; in force on every environment).** The
  accept of an OPENING
  barrier request runs five additional checks against the CURRENT mark and the
  request's own terms, protection rules for a product where the
  quote fixes the entry of a bilateral contract. (1) The accepted entry must
  lie within the market's published `barrierGuard.entryBandBps` of the mark
  (`GET /v1/instruments`; 50 bps unless the market sets its own, revision 3.70;
  the listing value and the calibration rule are in the knock-out contract
  specification; on the sandbox EUR-USD-KO, GBP-USD-KO and JPY-USD-KO carry
  20 bps and XAU-USD-KO, SP500-USD-KO and NDX100-USD-KO 30 bps from the date
  venue operations announce, and since revision 3.75 USDJPY-USD-KO 20 bps and
  US500-USD-KO and US100-USD-KO 30 bps, every other barrier listing 50 bps: read
  `GET /v1/instruments`, and quote within the tighter band on those markets),
  and the mark must be FRESH (younger than the
  market's staleness timeout, 60 s on the current barrier listings): `2039`
  QUOTE_OFF_MARKET otherwise, also answered while the mark is stale or absent,
  because there is nothing live to measure the quote against. (2) A
  `takeProfit` the mark has already reached (BUY: mark at or above it, SELL: at
  or below) rejects `2040` TAKE_PROFIT_REACHED; such a contract would terminate
  AT the take profit on the first tick after opening. (3) A `barrier` the mark
  has already reached or crossed (BUY: mark at or below it, SELL: at or above)
  rejects `2043` BARRIER_REACHED, the loss-side mirror of `2040`: such a
  contract would knock out on the first tick after opening, forfeiting the
  holder's premium and paying entry fees on the full notional. (4) The
  `takeProfit` must sit at least `barrierGuard.minTakeProfitBps` of the entry
  (10 bps unless the market sets its own) away from the accepted entry
  (`2041` TAKE_PROFIT_TOO_CLOSE below that) and (5) the `barrier` likewise, at
  least `barrierGuard.minBarrierBps` of the entry (10 bps unless the market
  sets its own) (`2042` BARRIER_TOO_CLOSE); distances exactly at the floor
  pass. Nothing trades on any of these rejects, the request stays open and the
  quote stays open too: request fresh quotes or re-request with wider levels.
  The EARLY-CLOSE accept is deliberately NOT
  banded against the mark: a fair close price is the contract's remaining
  value, not the spot, so its fences remain the strictly-inside-terms rule
  (`2027`), the close admission (`3001` / `3007`) and the instrument price
  band (`2003`). QUOTING MEMBERS: a quote is priced against the mark you saw,
  so refresh or withdraw your barrier quotes when the mark moves; an unexpired
  quote can otherwise be accepted against you right up to these fences.
- **Margin at accept.** Both parties are re-checked when a quote is accepted:
  if the requester no longer passes, the accept is rejected with `3001` (`3004`
  for the asset leg of a spot sell); if the quoting member no longer passes,
  with `3007`. The price band is also re-applied against the CURRENT mark at
  accept: a quote the market has drifted away from is stale and rejects `2003`
  rather than printing off-market. Member fitness is re-checked too: a
  reduce-only requester rejects `3002`, a reduce-only maker's quote resolves
  `2025`. A quote also commits the maker to the request's full size, so it is
  refused over the maker's order-size cap (`2013`): where the market publishes
  `maxOrderNotional`, the request's full size valued at the quoted entry price
  must fit under it; otherwise the base-unit `maxOrderQty` applies (section 2a).
- **Liquidation on RFQ markets.** A perp position on an RFQ market has no book
  for the venue to liquidate into: a member entering liquidation has such
  positions closed AT THE MARK against opposite-side holders in deleveraging
  order, profitable positions first, in one step; where the profitable side
  cannot absorb the whole position, the remaining opposite-side positions,
  including positions not in profit, are closed at the same mark price.
  Counterparties receive this as an `ADL`-flagged trade at the mark price;
  unlike classic auto-deleveraging it does not require the defaulter to be
  bankrupt.
- **Trade effects.** An accepted perp or spot RFQ publishes on the public
  trades feed and in `GET /v1/executions` / the WS `execution` channel flagged
  `RFQ`, with the requester as taker and the quoting member as maker for fees
  and reporting. It does NOT update the last-trade print, does not fire
  conditional orders and does not feed the circuit breaker: a bilaterally
  negotiated price is not book liquidity. An accepted barrier request prints
  the entry trade flagged `RFQ` in the same way; an accepted CLOSE request
  (revision 3.44) prints the closing trade flagged `BARRIER_CLOSE` (a new
  value in the `flags` vocabulary, ranking above `RFQ`), so trade history,
  drop copy and transaction reporting tell a contract's close from its entry.

**Barrier (knock-out) contracts.** A barrier market (`"kind": "BARRIER"`)
trades bilateral knock-out contracts, exclusively by RFQ. The requester is
always the **holder**: `BUY` holds long with the knock-out barrier BELOW the
entry, `SELL` holds short with the barrier ABOVE. The maker **writes** the
contract and quotes the ENTRY price; the barrier terms ride the RFQ itself
(`barrier: {level, expiryTs?, takeProfit?}`, open-ended when `expiryTs` is
omitted on a market that publishes no `maxContractLifeHours` (a market that
publishes one refuses an omitted or later `expiryTs` with `2027`, below),
knock-out only when `takeProfit` is omitted). CONDITIONAL rule
(revision 3.70): where the market publishes `barrierExpiryRequired` true on
`GET /v1/instruments`, the venue lists DATED contracts only and an opening
request without `expiryTs` (or with 0) answers `2027` with the message
`this venue lists dated barrier contracts only: send barrier.expiryTs` before
anything is journaled; close requests are untouched. The sandbox publishes
false and admits open-ended contracts; a production change of the flag is
announced to members first. MAXIMUM CONTRACT LIFE PER MARKET (revision 3.70,
register BROKER-86): a BARRIER market may publish `maxContractLifeHours` on
`GET /v1/instruments` and WebSocket `get.instruments`, the longest life of an
OPENING contract in whole hours, measured from the request. Where it is published
an opening request without `barrier.expiryTs`, or with one later than the request
time plus the cap, answers `2027`; an omitted `expiryTs` is open-ended only on a
market that publishes no cap. Close requests are untouched and open contracts run
to their terms. The sandbox's knock-out markets publish 8784 h (366 days) from
the date venue operations announce; a change of a market's cap is announced at
least seven Business Days ahead.

- **Holder economics: the premium (revision 3.45).** The holder's maximum
  loss on a contract is its PREMIUM: a share of the entry-to-barrier distance
  times quantity, `|entry - barrier| x qty x contract value x premiumBps /
  10000` (floored to 6 places; the contract value is the market's
  `contractMultiplier`, 1 on every barrier market by listing rule, so the
  premium is `|entry - barrier| x qty x premiumBps / 10000`), where
  `premiumBps` is FROZEN on the contract at the open: `10000 -
  knockOutRebateBps` on a normal contract (the market's
  knock-out REBATE, published on `GET /v1/instruments`; 1000 bps = 10 percent
  on every barrier market listed today, so the premium is 90 percent of the
  distance), the market's `strictTakeProfitPremiumBps` on a STRICT take-profit
  contract (below), and 10000, the full distance, on every contract opened
  before the rebate existed. The holder margins exactly the premium at entry
  (IM equals MM), and the requirement then tracks the most it can still lose:
  the REMAINING mark-to-barrier distance less the rebate (the premium plus the
  unrealized P&L, floored at 0). An adverse move releases requirement exactly as fast as it costs
  equity, so the contract can never liquidate its holder, while a favourable
  move grows the requirement and keeps the profit reserved until close. When
  the mark touches the barrier the contract terminates AT the barrier price, a
  guaranteed stop with no gap risk even if the mark gaps through the level, and
  the holder loses exactly the premium: the writer receives it, and the rebate
  share of the distance is what the holder recovers from the writer at that
  moment. Every other settlement (expiry of a normal contract, tear-up, final
  settlement, an early close) is the price P&L floored at minus the premium and,
  when a take profit exists, capped at the potential profit
  `|takeProfit - entry| x qty x contract value`; a take-profit touch pays that
  potential profit in full. The premium and its share ride every barrier
  surface as `premium` (money string) and `premiumBps` (int): the RFQ views
  (indicative at the current mark until a quote is accepted, since the entry is
  the quoted price), the accept response, `GET /v1/barriers` rows and the WS
  `barriers` frames. The knock-out and, when set, the take profit are the only
  AUTOMATIC holder-side close-outs; the holder can also ask its writer for an
  early close by RFQ (revision 3.44, below).
- **Strict take profit (revision 3.45), the cheaper short-dated variant.**
  `barrier.strictTakeProfit: true` asks for a contract that pays ONLY if the
  take profit is touched before the contract expiry: at the expiry without a
  touch the holder loses the whole (strict) premium whatever the mark, however
  close it is (`terminationReason` `EXPIRY`, holder `realizedPnl` = minus the
  premium), and "before" is strict: a price tick that arrives at or after
  `expiryTs` settles the expiry even if that tick is at or beyond the take
  profit (the strict contract tests its expiry before its take profit, unlike a
  normal contract, whose take profit is guaranteed on such a tick); a knock-out
  loses the premium as on any contract; a take-profit
  touch pays `|takeProfit - entry| x qty x contract value` in full; an early
  close by RFQ settles at the writer's price, clamped to [minus the premium,
  plus the potential profit] like every early close. It requires a
  `takeProfit`, an `expiryTs` at most 24 hours after the request, and a market
  whose `strictTakeProfitPremiumBps` is nonzero (5000 bps = 50 percent of the
  distance on every barrier market listed today, against 90 percent for a
  normal contract), else `2027`; the market's share is re-checked at accept and
  frozen onto the contract as `premiumBps`. Every RFQ view, the accept
  response, `GET /v1/barriers` rows and the WS `barriers` frames carry
  `strictTakeProfit` (bool). Absent or false = a normal contract, so a client
  that never sends the flag sees only additive fields.
- **Take profit (revision 3.43), the optional second exit.** `barrier.takeProfit`
  is a price on the PROFIT side of the entry: above it for a `BUY` (long
  holder), below it for a `SELL` (short holder), so the barrier and the take
  profit always sit on opposite sides of the entry. It is validated against the
  current mark at create and against the QUOTED entry at quote and accept
  (strictly beyond it by at least one tick, else `2027`), exactly like the
  barrier level. When the mark reaches the take profit (long: mark at or above
  it; short: at or below) the contract terminates AT the take-profit price,
  fee-free like a knock-out, settling as `BARRIERSETTLE` zero-sum: the holder
  receives `|takeProfit - entry| x qty` in contract value, the writer pays it.
  A mark that gaps past the level still settles AT the level (guaranteed on
  both sides), and one tick can never cross both levels because they lie on
  opposite sides of the entry. Holder margin is unchanged by a take profit
  (still the premium, tracking the remaining mark-to-barrier distance). Expiry
  with a take profit set behaves as without one on a normal contract: mark
  settlement on the first tick at or past `expiryTs` (floored at minus the
  premium, capped at the potential profit); on a STRICT contract the expiry
  forfeits the premium (revision 3.45, above).
- **Writer economics.** The writer margins like a perp, on the market's risk
  tiers at current notional; when the contract carries a take profit the
  writer's maximum loss is bounded by the take-profit distance times quantity,
  so its requirement is the SMALLER of that tiered figure and the remaining
  mark-to-take-profit distance times `qty` times the contract value (a bounded
  contract never over-margins the writer). The knock-out rebate the writer
  gives back at a knock-out (revision 3.45) adds no requirement of its own: at
  a knock-out the writer still receives the premium, its worst case remains the
  take-profit side, which the tiered figure and the take-profit bound already
  cover. A member that enters liquidation has
  its barrier contracts torn up at the barrier-clamped mark (the counterparty
  receives a fair close, never beyond the barrier or the take profit) and its
  open RFQs and quotes withdrawn.
- **No funding** on barrier markets. A contract with an expiry settles at the
  mark on the first price tick at or past `expiryTs`.
- **Early close by RFQ (revision 3.44).** The HOLDER of an open contract may
  ask its WRITER to close it early: `POST /v1/rfq` with `symbol` (the
  contract's market) and `closeContractId` (the contract's id from
  `GET /v1/barriers`, `role` `HOLDER`), and NO `side`, `qty` or `barrier`
  (any of them present rejects `2013`): the venue derives the CLOSING side (a
  long holder `SELL`s, a short holder `BUY`s), the full contract quantity and
  the contract's terms, and the request carries them back on every RFQ view
  together with `closeContractId`. `expiresInMs`, `clOrdId` and `reg` behave
  as on any request; `clientUid` may be omitted (it defaults to the contract's
  holder-side client) or must equal it (`2010`, whose message names this case);
  an omitted `clientUid` on a close request is NOT read as house flow by the
  trading-capacity gate (section 3a), so an MTCH or AOTC member sends the
  documented body unchanged. Only the holder may request,
  at most ONE close request per contract at a time, and a `closeContractId`
  that is not an open contract you hold on this market rejects `2035` (also
  when a close request is already open for it, and when the contract
  terminates between the steps). Who sees it: the request is on the WRITER's
  board only (`GET /v1/rfq/open` and the WS `rfq` `created` frame, delivered
  to the writer with `role` `WRITER`; the writer receives the request's
  terminal `canceled` / `expired` frame the same way, revision 3.48, so a
  board fed from the stream closes on both sides); no other member ever sees it, and any
  other member quoting it is answered `2024`. The writer's quote is the price
  the contract CLOSES at: tick-aligned (`2002`), inside the price band against
  the mark (`2003`) as always, and STRICTLY INSIDE the contract's terms
  relative to the holder's direction, `barrier < price < takeProfit` for a long
  holder (when a take profit exists) and mirrored for a short holder, else
  `2027`: a price at or beyond the barrier would exceed the guaranteed maximum
  loss, at or beyond the take profit the guaranteed maximum gain. Re-quotes
  replace as always. The holder's accept (`POST /v1/rfq/{rfqId}/accept`)
  closes the contract AT the quoted price: it prints the closing trade
  (`BARRIER_CLOSE` flag) which pays ordinary maker/taker fees exactly like the
  entry trade (holder taker, writer maker, on `qty` x price x contract value),
  then settles the contract `BARRIERSETTLE` zero-sum (holder P&L =
  direction x (price - entry) x `qty` x contract value, the writer the
  negation) with `terminationReason` `EARLY_CLOSE`, and the accept response
  carries `contractId` (the closed contract), `closeContractId`, `settlePrice`
  (the quoted price) and your `realizedPnl` (holder-signed, a 6-place decimal
  string like every money field; since revision 3.45 clamped to [minus the
  premium, plus the potential profit]) beside the trade fields. The accept re-checks
  that the contract is still open and held (`2035`), that the quote still lies
  inside the terms (`2027`), the band against the current mark (`2003`) and
  member statuses as always, and (revision 3.47) that the close leaves both
  parties solvent: closing RELEASES the contract's margin, so a close whose
  settlement plus fee costs a party no more than it releases is always admitted
  (a holder funded to exactly its premium can close at any price where the
  released premium covers the loss and the fee, and a member under initial
  margin can still close at or near the mark), and otherwise the party's
  available balance must stay non-negative after the settlement and the fee,
  else `3001` (holder) or `3007` (writer, so a quote far off the mark that the
  writer's released margin cannot cover is not executable) with nothing moved.
  The exact-money fence stays (`2027`). Close requests pass every other RFQ
  gate unchanged, with the same codes an ordinary request answers on each
  step. Create: `3003` on a halted market, `3006` delisted, `3005` while the
  market has no mark yet, `3002` on a reduce-only or stale market or when you
  are in reduce-only, and the request is valued at the current mark through
  the per-order size cap (`2013`) and the minimum notional (`2028`). Quote:
  `3003` on a halted, delisted, reduce-only or stale market, `3002` when the
  writer is suspended or in reduce-only, and the writer's quote passes the
  size cap (`2013`) and the minimum notional (`2028`) at its price. Accept:
  `3003` on a halted, delisted, reduce-only or stale market, `3014` while a
  GRID perpetual's funding catch-up is pending (revision 3.70, section 6;
  barrier and swap markets never fund, so only an RFQ-mode perpetual can answer
  it), `3002` when you
  are suspended, in reduce-only, in liquidation or defaulted, and `2025` when
  the writer is no longer fit to trade (suspended, reduce-only, in liquidation
  or defaulted), exactly as for any quote. A contract you cannot close for one
  of these reasons is still protected by its barrier and settles by knock-out,
  take profit or expiry. Two of the gates are sizes rather than states: a
  contract whose whole quantity is worth MORE than the market's
  `maxOrderNotional` at the current mark (`2013`, revision 3.70) or LESS than
  its `minNotional` at the current mark (`2028`; 100 USD on every barrier
  market since revision 3.71, the close request valued at the contract's whole
  quantity and the writer's close quote at its price) cannot be closed early
  by request and ends at its barrier, its take profit or its expiry;
  termination and settlement are never floored, so a contract of any size
  always settles.
  Coupling: while a close request is open the contract keeps a `closeRfqId`
  and refuses a second one; if the contract terminates first (knock-out, take
  profit, expiry, tear-up, final settlement) the venue CANCELS the open close
  request in the same command (an `rfq` `canceled` frame, an ordinary cancel),
  and when the request ends (accept, cancel, expiry) the contract is free for a
  new one. Cancelling a close request (`POST /v1/rfq/{rfqId}/cancel`) is free
  like every cancel.
- **Contract identifier (revision 3.70).** A barrier contract is identified for
  transaction reporting by the ISO 6166 ISIN of its TERM SET (the market, the
  holder direction, the barrier level, the expiry date or open-ended and, as the
  venue has it defined with the Derivatives Service Bureau, the take profit and
  the strict variant), allocated by the venue after the opening request. Every
  row of `GET /v1/barriers` (live and closed, holder and writer alike) and the
  accept result of an opening or an early close carry it as `isin`; the value is
  null, and therefore omitted, until the venue has allocated it (a closed row
  shows a later allocation too) and always null where the venue files at market
  granularity, which is the sandbox. Two contracts on the same terms share one
  ISIN; the market's own `isin` on `GET /v1/instruments` is null on production
  because barrier contracts carry their own. The WebSocket `barriers` frames are
  unchanged; read the contract row or the accept result for the identifier. A
  member reporting its own barrier transactions adopts the contract ISIN from
  these surfaces.
- **Fees.** Entry pays ordinary maker/taker fees on the entry notional (holder
  taker, writer maker); an early close pays them again on the closing notional
  (same roles); knock-out, take profit, expiry and every other venue-initiated
  settlement is fee-free. Settlement P&L posts to the ledger (WS `wallet`
  channel) as kind `BARRIERSETTLE`, zero-sum between the parties.
- **Bilateral, not positions.** Barrier contracts never appear in
  `GET /v1/positions` or in open interest. They live on `GET /v1/barriers`:
  `contractId`, `role` (`HOLDER` | `WRITER`), `direction` (`LONG` | `SHORT`),
  `qty`, `entry`, `barrier`, `takeProfit` (omitted on a knock-out-only
  contract), `strictTakeProfit`, `premium` and `premiumBps` (revision 3.45,
  above), `status` (`OPEN` | `CLOSED`), optional `expiryTs`, `clientUid`,
  `openedTs`, and on live rows `markPrice`, `barrierDistance` (distance from
  mark to the knock-out as a decimal fraction of mark), `unrealizedPnl`
  (role-signed, floored at minus the premium for the holder) and `margin`.
  Closed rows (`?status=CLOSED` or `ALL`) replace
  the mark-derived figures with `settlePrice` (the barrier on a knock-out, the
  take-profit level on a take profit, the writer's accepted quote on an early
  close, the terms-clamped mark otherwise), role-signed `realizedPnl`,
  `terminationReason` (`KNOCK_OUT` | `TAKE_PROFIT` | `EXPIRY` | `TEAR_UP` |
  `FINAL_SETTLEMENT` | `EARLY_CLOSE`) and `closedTs`.
- A barrier market runs its own mark price feed under its own symbol (sandbox:
  `BTC-USD-KO`); knock-out, expiry and margin all use that mark.
- **How many contracts you may hold at once.** Alongside the size caps, an accept
  is refused if it would take you past **256 open barrier contracts** (holder or
  writer, all barrier markets together), or take the market past **4096** open
  contracts. Your own limit answers `2011`; the quoting member's answers `3007`,
  the same codes the size caps use. These bound how much work each contract adds
  to every margin calculation and every price tick, they are not risk limits, and
  they release as contracts knock out, expire or settle. No member is near either.

**Writer transparency (revision 3.57).** A member asked to quote a request, and
the writer of a live barrier contract, can see the CAPACITY behind the flow
without ever learning the end client's identity:

- `capacity` is `FOR_CLIENT` when the request (or the contract's holder side)
  carries an end-client reference, and `OWN_ACCOUNT` when it rides the
  requesting member's house account (a request submitted without a `clientUid`
  is house flow by definition and reads back as `OWN_ACCOUNT`).
- `clientTag` is a 16-character lowercase hex pseudonym of the end client,
  present only on `FOR_CLIENT` rows (null or omitted otherwise). It is STABLE
  FOR YOU: the same end client of the same requesting member always tags the
  same toward you, across requests, contracts and reconnects, so you can
  recognise repeat flow and shape your quotes accordingly. It is UNLINKABLE
  ACROSS MEMBERS: your tag and any other member's tag for the same client never
  agree, and the raw `clientUid` is never derivable from it (the venue computes
  the tag from a secret it holds, per viewer). Tags are not portable between
  environments.
- The pair appears on: every `GET /v1/rfq/open` board row, the writer's copy of
  a close request's WS `rfq` frames, your WRITER rows on `GET /v1/barriers`
  (live and closed; `clientTag` is your tag of the HOLDER's end client) and the
  WS `barriers` frames delivered to you as the writer. Your own views as the
  requester or holder are unchanged: they keep carrying your own `clientUid`,
  never a tag.
- Since revision 3.58 the same writer-visible surfaces additionally carry, on
  barrier markets, `levelOrigin` (`CLIENT` \| `DERIVED`: whether the end client
  typed the requested levels or a routing system derived them) and, on
  `FOR_CLIENT` rows only, `clientClass` (`RETAIL` \| `PROFESSIONAL` \|
  `ELIGIBLE`, the end client's MiFID II categorisation), both frozen onto the
  contract at accept (a close request shows its contract's frozen pair).
  Neither field moves any price, margin or settlement figure; they exist so a
  writer can shape its quoting to the risk profile of the flow.

**Transports.** RFQ is available over REST and WebSocket only (private
channels `rfq` and `barriers`, section 10.3); it is not offered over FIX.

## 6c. Batch order entry, amendment and cancellation (private)

Three batch routes carry 1 to 50 entries in one signed message:
`POST /v1/orders/batch` places complete, independent orders, and, since
revision 3.74, `POST /v1/orders/batch/amend` amends and
`POST /v1/orders/batch/cancel` cancels orders you already have. Each is a
transport optimisation, never a transaction: every entry the venue submits
reaches the matching engine as the ordinary single order, amendment or
cancellation, the entries are answered one row each in array order, and one
entry's refusal never stops its neighbours. The placement batch comes first;
the amendment and cancellation batches follow in their own subsections.

`POST /v1/orders/batch` submits **1 to 50 complete, independent orders in one
signed message**. The worked example below is the mass-close use case: one
message unwinds three end clients' long positions with three `reduceOnly`
sells, each entry tagged with the client it closes:

```json
{
  "orders": [
    { "symbol": "BTC-USD-PERP", "side": "SELL", "type": "LIMIT", "tif": "GTC",
      "price": "65000", "qty": "0.2", "reduceOnly": true,
      "clOrdId": "close-cl-a-1", "clientUid": "CLIENT-A" },
    { "symbol": "BTC-USD-PERP", "side": "SELL", "type": "LIMIT", "tif": "GTC",
      "price": "65000", "qty": "0.5", "reduceOnly": true,
      "clOrdId": "close-cl-b-1", "clientUid": "CLIENT-B" },
    { "symbol": "BTC-USD-PERP", "side": "SELL", "type": "LIMIT", "tif": "GTC",
      "price": "65000", "qty": "0.1", "reduceOnly": true,
      "clOrdId": "close-cl-c-1", "clientUid": "CLIENT-C" }
  ]
}
```

Every entry is a full `POST /v1/orders` body with its **own `clOrdId` and its
own `clientUid`**; that is the point of the feature: one message, N
client-tagged orders, for example one `reduceOnly` close per end client when
you unwind a book. Each entry passes the identical validation a single order
passes, including `reduceOnly` semantics, the `minNotional` floor and its
reduce-only exemption (section 2a), the order-size and position caps (2013
and 2011, section 2a: each entry is one message against the order-size cap,
and an earlier entry's working remainder counts in a later entry's worst
case), strict unknown-field rejection, and the reserved `KX-` namespace
guard. **The `clientUid` tags do not make `reduceOnly` client-scoped**: each
entry is still measured against your MEMBER NET position, and this example
works only because all three entries reduce the same member long. Read the
reduce-only warning in section 6 before you use a batch to close individual
end clients.

**The entry parse.** An entry's member names are case-sensitive (`Qty` is an
unknown field, answered `2017` naming it), where the single `POST /v1/orders`
body matches member names without regard to case, and a value of the wrong
JSON type in an entry (a number where the schema has a string) answers `2017`
"malformed request body". Send the names exactly as this reference spells them.

**Non-atomic by design.** The batch is a transport optimisation, not a
transaction. Each entry validates and executes independently, exactly as if
you had sent it as a single `POST /v1/orders` (the parse above apart); one
entry's rejection never affects its neighbours, and there is no all-or-nothing
execution mode.

**Sequencing.** Entries are submitted to the matching engine in array order,
and array order is the engine sequencing order among the entries of your
batch. Two crossing entries in one batch therefore behave deterministically
(the earlier entry rests, the later one matches it). Other members' traffic
may interleave between your entries; a batch holds no lock on the book.

**Response.** HTTP 200 with envelope `code` 0 and `data.results`, one entry
per order in array order, even when every entry failed:

- a successful entry has exactly the shape of the single-order place response
  `data` (final status plus any executions produced while matching;
  engine-level rejects such as `2028`, an order-size cap `2013` or a position
  cap `2011` ride that payload as `rejectCode` with `status` `REJECTED`,
  exactly like the single route);
- a failure entry is `{"code": ..., "msg": ..., "clOrdId": ...}`, each
  echoing that entry's `clOrdId`: a refusal of the gateway (strict
  unknown-field rejects and wrong-typed values `2017`, the reserved `KX-`
  namespace `2020`, grid misalignment `2002`; a `qty` or price that is not a
  decimal numeral at all is `2013`, section 9; and so on), the matching
  engine's `2009` for a duplicate `clOrdId` with a different payload, `5001`
  for an entry the venue did not accept (after an entry the venue's intake
  refused as it was submitted, every later entry answers `5001` "not
  submitted: the venue's intake refused an earlier entry of this batch"; on a
  clustered deployment the replicated intake can also refuse an entry after
  the later ones were submitted, and those may take effect, so read every row:
  "What the batch routes share" below), and, since revision 3.74, `5000`
  "outcome unknown" for an entry whose outcome the venue could not confirm.
  Reconcile a `5000` row by its `clOrdId` and resend it only with the SAME
  `clOrdId`: an identical payload returns the original order (`duplicate`
  true) when the first attempt took effect, and places it when it did not.
  Before revision 3.74 such a fault answered the whole request with an
  undocumented HTTP `500` code `5000` (REST) or `2013` "malformed request"
  (WebSocket), hiding every other entry's outcome, and an entry appended to
  the log before a loss of leadership answered `5001`; it now answers `5000`.

For the worked example above, with 0.2 resting on the bid at 65000, array
order means the first entry takes that bid and fills while the second and
third rest working:

```json
{
  "code": 0, "msg": "ok",
  "data": {
    "results": [
      { "orderId": 887731, "clOrdId": "close-cl-a-1", "symbol": "BTC-USD-PERP",
        "side": "SELL", "type": "LIMIT", "tif": "GTC", "price": "65000",
        "qty": "0.2", "remainingQty": "0", "reduceOnly": true,
        "clientUid": "CLIENT-A", "status": "FILLED",
        "executions": [
          { "tradeId": 55131, "orderId": 887731, "symbol": "BTC-USD-PERP",
            "side": "SELL", "price": "65000", "qty": "0.2", "fee": "6.5",
            "role": "TAKER", "flags": "NORMAL", "clOrdId": "close-cl-a-1",
            "refSource": "MEMBER", "clientUid": "CLIENT-A",
            "ts": 1785000000117 } ],
        "acceptedTs": 1785000000117 },
      { "orderId": 887732, "clOrdId": "close-cl-b-1", "symbol": "BTC-USD-PERP",
        "side": "SELL", "type": "LIMIT", "tif": "GTC", "price": "65000",
        "qty": "0.5", "remainingQty": "0.5", "reduceOnly": true,
        "clientUid": "CLIENT-B", "status": "NEW", "acceptedTs": 1785000000117 },
      { "orderId": 887733, "clOrdId": "close-cl-c-1", "symbol": "BTC-USD-PERP",
        "side": "SELL", "type": "LIMIT", "tif": "GTC", "price": "65000",
        "qty": "0.1", "remainingQty": "0.1", "reduceOnly": true,
        "clientUid": "CLIENT-C", "status": "NEW", "acceptedTs": 1785000000117 }
    ]
  },
  "asOfSeq": 104874, "ts": 1785000000119
}
```

Had one entry failed gateway validation instead, only that row would change,
for example `{"code": 2002, "msg": "price not on the tick grid, or qty not an
exact multiple of qtyStep", "clOrdId": "close-cl-c-1"}`; its neighbours stand,
and you resend only the corrected entry.

Envelope-level errors are limited to authentication (`1001`), the read-only
credential reject (`1005`), throttle (`1003`), a malformed body or an entry
count outside 1 to 50 (`2002` with a descriptive `msg`), the engine-busy shed
(`5001`), the order-to-trade block (`1004`) and, on a clustered deployment's
follower node, `5030` with `leaderId` and `leader` (REST, and since revision
3.76 the WebSocket `orders.batch` ack too, before anything is charged; it
answered `5001` rows there before). The `5001` and the `1004` come after the
charge: the N units stay spent.

**Rate limits.** The batch consumes one order-budget unit PER ENTRY, charged
up front, all or nothing: if fewer than N units are available the whole batch
answers `1003` with `retryAfterMs` and consumes nothing. Every entry counts as
one order message under the order-to-trade regime (section 4), whatever its
per-entry outcome.

**Transports.** The identical operation is available on the private WebSocket
as op `orders.batch` (section 10.5). The three batch routes are REST and
WebSocket only; none is offered over FIX, where you send singles (35=D, 35=G,
35=F) instead.

### Batch amendment, `POST /v1/orders/batch/amend` (revision 3.74)

`POST /v1/orders/batch/amend` amends **1 to 50 of your own orders in one
signed message**; the WebSocket twin is op `orders.batchAmend` (section 10.5).
The example resizes one order, reprices another and does both on a third:

```json
{
  "amends": [
    { "orderId": 887731, "qty": "0.15" },
    { "orderId": 887732, "price": "64990.5" },
    { "orderId": 887733, "price": "65010", "qty": "0.2" }
  ]
}
```

**The entries.** The body is an object whose ONLY member is `amends` (an
`atomic` flag, or the `orders` member of a placement body sent here, answers
`2002` for the whole message). Each entry is `{orderId, price?, qty?}`:
`orderId` a JSON integer of at least 1, the venue order id of one of your
orders; `price` and `qty` JSON strings with exactly the meaning of the
`PUT /v1/orders/{orderId}` body (`qty` is the new TOTAL quantity, fills
included). An entry with neither is the single amendment's accepted no-op:
amended in place, charged and counted. Each entry the venue submits is
processed by the matching engine EXACTLY as `PUT /v1/orders/{orderId}`: a
quantity decrease at the same price keeps queue priority, any price change or
quantity increase is cancel+new, and every engine check of the single
amendment applies at the current state (section 6, Manage orders), also to an
order that already stands at the requested terms. The gateway parses an entry
like a placement batch entry: member names are case-sensitive (`Qty` or a
`clOrdId` is an unknown field, `2017` naming it; an amendment names its order
by `orderId` alone, on every transport) and a value of the wrong JSON type (a
string `orderId`, a numeric `price` or `qty`, a fraction) answers `2017`
"malformed request body". After the parse, `2013` answers an entry that is
not a JSON object ("each amends entry must be a JSON object"), an entry
without `orderId` ("orderId is required"), an `orderId` below 1 ("orderId must
be at least 1") and a `price` or `qty` that is not a decimal numeral; a
numeral off the order's grid is `2002`. An order named by two entries: the
first entry runs and every later entry naming it answers `2013` "order N is
already named by entry k of this batch", where k is the earlier entry's index
in the array, counted from 0, the index of its row in `results`; nothing is
submitted for the later entry.

**Non-atomic by design.** The entries reach the matching engine in array
order, and array order is the engine sequencing order among them; other
members' traffic may interleave between your entries, and the batch holds no
lock on the book. A refused entry never stops a later one: every entry is
submitted before the first verdict, so entry k can be refused (a fill or an
earlier entry's self-trade cancel took its order, `2014`; a band move, `2003`;
margin, `3001`) while entry k+1 takes effect. When one entry depends on
another's outcome, send the dependent entry in a SECOND message after you read
the first message's rows.

**Ordering inside a batch.** An amendment that reprices re-matches the order
as a taker outside a call auction, so an entry that moves your bid to or
through your own ask meets it under your self-trade policy: with the same
`clientUid` the default `CANCEL_RESTING` cancels the ask `STP_CANCEL` (a later
entry naming it answers `2014`), and with a different `clientUid` your own
orders TRADE under the default end-client scope. So in a shift up amend the
asks before the bids, and in a shift down the bids before the asks (the side
moving away from the other first); size down before you size up, because each
entry's margin check sees the state the earlier entries left and a size-down
keeps its priority. The advice bounds the risk ONLY when every earlier entry
takes effect: a refused earlier entry leaves the later one to cross it, and the
moved order can also take third-party liquidity at the stale level. Under a
margin shortfall, a reduce-only session, a band move or the funding catch-up
fence a size-down amendment is refused like any other amendment, so de-risk
with the cancellation batch, never with size-down amendments.

**Response.** HTTP 200 with envelope `code` 0, `data.results` one row per
entry in array order, and `asOfSeq`, even when every entry failed. A row that
carries `code` is a FAILURE row `{"code": ..., "msg": ..., "orderId": ...}`
(`orderId` echoed only when the entry carried a JSON integer of at least 1,
`clOrdId` only when it carried a JSON string; otherwise the member is omitted).
Any other row is the ORDER as of the response's `asOfSeq`, exactly the
`PUT /v1/orders/{orderId}` `data` (the open order, else its terminal record).
If the venue could not read your orders back after the batch (rare: its
intake full, or the node faulted), such a row is instead the order's last
state change among the batch's own entries, rendered as the `order` channel
renders it (`acceptedTs` is that change's time and a part-filled working
order reads `NEW`), and `asOfSeq` is then the lowest seqNo at which a
still-working row was taken, or the seqNo before it when the command that
made that row's change also traded the order (the row is then the order
before those fills): every row is the order as of `asOfSeq` or later, and
the `order` and `execution` events after `asOfSeq` bring it up to date.
Read a row as two gates: a row carrying `code` is a failure row; otherwise
`status` tells whether the order still stands. A success row can read
`REJECTED` (`rejectCode` `2005`, a post-only order repriced to cross; `2015`
under `REJECT_TAKER`) or `CANCELED` (`STP_CANCEL`): THAT LEVEL IS GONE, place it
again with a placement batch. A re-match that traded reads `FILLED` or
`PARTIALLY_FILLED`; its executions ride the `execution` channel and
`GET /v1/executions`, never the row. `rejectCode` `5005` on a `NEW` or
`PARTIALLY_FILLED` row means the amendment took effect before a contained
fault of its command: reconcile, never resend it unchanged.

```json
{ "code": 0, "msg": "ok",
  "data": { "results": [
    { "orderId": 887731, "clOrdId": "q-bid-1", "symbol": "BTC-USD-PERP", "side": "BUY",
      "type": "LIMIT", "tif": "GTC", "price": "64995", "qty": "0.15", "remainingQty": "0.15",
      "reduceOnly": false, "clientUid": "H1", "status": "NEW", "acceptedTs": 1785000000000 },
    { "code": 2014, "msg": "order not found", "orderId": 887732 },
    { "orderId": 887733, "clOrdId": "q-bid-3", "symbol": "BTC-USD-PERP", "side": "BUY",
      "type": "LIMIT", "tif": "POST_ONLY", "price": "65010", "qty": "0.2", "remainingQty": "0.2",
      "reduceOnly": false, "clientUid": "H1", "status": "REJECTED", "rejectCode": 2005,
      "acceptedTs": 1785000000118 } ] },
  "asOfSeq": 104880, "ts": 1785000000121 }
```

The first row kept its `acceptedTs` (a size-down in place keeps the
acceptance), the second named an order that had filled or been cancelled since
you last read it (or that is not yours: the answer is the same), and the third
was repriced to cross and removed: a SUCCESS row whose level is gone.

The per-entry refusals are `2017`, `2013`, `2014` (the order is not found, not
yours or already terminal; nothing is submitted for it), `1004` (the order's
market is blocked by the order-to-trade regime), `2049` (a frozen DLT
instrument or client), `2002` (off the grid), every refusal of the single
amendment by the matching engine (`5002`, `3003`, `3006`, `3002`, `3009`,
`3014`, `2013`, `2003`, `2028`, `2011`, `3001`, `3004`, `2008`), `5005` when a
contained fault left nothing in effect, and `5001` and `5000` (below, "What
the batch routes share").

**Rate limits and the order-to-trade regime.** One order-budget unit per
entry, charged up front, all or nothing, AFTER the venue's engine-busy check:
`1003` (with `retryAfterMs`) and an engine-busy `5001` consume nothing, while
the units of a batch whose entries are refused afterwards (`2014`, `1004`,
`2049`, a grid refusal, an order the venue could not look up) stay spent. A
member whose per-minute or per-second order figure is below N can never be
served an N-entry batch: the whole batch answers `1003` until N units are
available at once. An entry naming an order on a market where the regime
blocks you answers a `1004` row, counts nothing and leaves the other entries
running; every entry the venue hands to the matching engine counts as one
order message on its order's market (and your account-wide ratio), and a
`2014`, `1004`, `2049` or grid row counts nothing. The DEA controls (`3010`,
`3011`), the Trader ID (`2021`) and the trading capacity (`2033`) are placement
gates: as on the single amendment, none of them is checked on an amendment
entry. Budget: 24 levels requoted 5 times a second on
one market are 120 order messages a second, 7,200 units a minute; send only
the levels that changed, because a no-op entry costs a unit and an order
message like any other.

**Retrying.** There is no batch key. An amendment's values are absolute, so an
entry sent again after it took effect USUALLY finds the order at those terms
and amends it in place (the same price and a quantity not increased keep the
priority), spending one unit and one order message; after a fill the same
total quantity stays valid unless it is at or below the filled quantity
(`2013`). But every entry re-runs every engine check at the current state: a
band move (`2003`), the funding catch-up fence (`3014`), a reduce-only session
(`3002`) or a margin shortfall (`3001`) refuses the resend of an order that
ALREADY stands at the requested terms. A refusal on a resend therefore does
not mean the first attempt failed: read your orders back
(`GET /v1/orders/open`) before you act on it, and read back a batch whose
answer was lost before you send anything again, never resend it whole blind.

### Batch cancellation, `POST /v1/orders/batch/cancel` (revision 3.74)

`POST /v1/orders/batch/cancel` cancels **1 to 50 of your own orders in one
signed message**; the WebSocket twin is op `orders.batchCancel` (section
10.5). An entry names its order by `orderId` or by your `clOrdId`, exactly
one:

```json
{
  "cancels": [
    { "orderId": 887734 },
    { "clOrdId": "q-ask-3" }
  ]
}
```

**The entries.** The body is an object whose ONLY member is `cancels` (else
`2002` for the whole message). Each entry is `{"orderId": ...}` (a JSON
integer of at least 1) or `{"clOrdId": ...}` (a non-empty JSON string,
compared character for character and never refused for padding: a cancel by
reference is a lookup). The parse is the amendment batch's: case-sensitive
names and a wrong JSON type answer `2017`; `2013` answers an entry that is not
a JSON object, an entry with both identifiers or neither ("a cancels entry
names exactly one of orderId and clOrdId"), an `orderId` below 1 and an empty
`clOrdId` ("clOrdId must not be empty").

**The venue looks every entry up first.** One read resolves every well-formed
entry to an OPEN order of yours: an `orderId` that is live and yours, a
`clOrdId` that names an open order of yours carrying exactly that reference.
An entry that resolves to nothing (not found, not yours, already terminal, or
a reference naming no open order of yours) answers `2014` and nothing is
submitted for it; every other entry is processed by the matching engine
exactly as `DELETE /v1/orders/{orderId}`. An entry resolving to an order an
earlier entry already names answers `2013` "order N is already named by entry
k of this batch", so one order named once by `orderId` and once by `clOrdId`
is caught too. A cancellation entry is never refused by a halt, a suspension,
a risk state, a reduce-only or post-only session or a DLT freeze: a cancel
checks only that the order exists and is yours. An order that fills between
the lookup and its cancel answers the matching engine's `2014`.

**Free, and never blocked.** No order-budget unit, never throttled, never
blocked by the order-to-trade regime. Each submitted entry counts one order
message on your account-wide ratio and each cancelled order one on its market,
as a single cancel does.

**One cancellation batch of yours at a time on a node.** A venue node
processes one cancellation batch of yours at a time, REST and WebSocket
together: a later batch waits, in arrival order, for the earlier one's answer,
so its lookup sees the earlier batch's effects and two batches never both
cancel the same order. When eight of your cancellation batches are already
waiting on the node, the next answers `5001` "engine busy: eight cancellation
batches of your account are already queued on this node; nothing was
submitted". Your single cancels, `DELETE /v1/orders?symbol=&side=`,
`order.cancelAll` and cancel on disconnect are never held by it. A WebSocket
session processes one message at a time, so while a batch runs the socket's
next frame waits: keep a SECOND private socket for urgent cancels.

**Response.** HTTP 200 with envelope `code` 0, `data.results` in array order
and `asOfSeq` (the highest seqNo of the entries submitted, else the lookup's).
A cancelled row is the single cancel's `data`, the cancelled order: `status`
`CANCELED`, `cancelReason` `USER_REQUESTED`, an untriggered conditional
included; `rejectCode` `5005` on it means the cancel took effect before a
contained fault of its command (reconcile, never resend it unchanged). A
failure row is `{"code": ..., "msg": ..., "orderId": ..., "clOrdId": ...}`:
`orderId` when the entry carried one of at least 1 or its reference resolved
to one, `clOrdId` when the entry carried a JSON string. The per-entry refusals
are `2017`, `2013`, `2014`, `5005` when a contained fault left nothing in
effect, and `5001` and `5000` (below).

### What the batch routes share (revision 3.74)

**Envelope errors come only before anything is submitted.** Authentication
`1001`, the read-only credential `1005`, a malformed body or an entry count
outside 1 to 50 (`2002`, the `msg` names the rule), `5001` when the venue
sheds (the amendment batch at the engine-busy depth before the charge, the
cancellation batch only when the venue's intake is nearly full), when it could
not look the orders up ("engine busy: the venue could not look the orders up;
nothing was submitted"; the amendment batch's units are then spent) or when
eight of your cancellation batches already wait, `5030` with `leaderId` and
`leader` on a clustered deployment's follower node (REST and both WebSocket
ops, before anything is charged or counted), and for the amendment batch the
licensing profile `2034` (production) and `1003`. After the first submission
every outcome is a row.

**The rows the venue could not settle.** An entry the venue did not accept
answers `5001`. When the venue's intake refuses an entry as it is submitted,
the venue submits nothing after it: every later entry that would have been
submitted answers `5001` "not submitted: the venue's intake refused an
earlier entry of this batch", so the entries the intake accepted are the
first ones of your array. On a single-node deployment every intake refusal is
of that kind. On a clustered deployment the replicated intake can instead
refuse an entry after the venue has already submitted the entries behind it:
that entry answers `5001` ("engine busy") while the later entries answer
their own outcomes and may have taken effect, and it counts as one order
message like every entry the venue handed to the matching engine. So read
every row: never infer from the first `5001` that nothing after it took
effect. An entry whose outcome the venue could not confirm answers
`5000` "outcome unknown: the venue could not confirm whether this entry took
effect; read the order back on the leader before sending it again". A `5005`
row with nothing in effect, or an order row carrying `rejectCode` `5005`, is
reconciled before anything is sent again and never resent as it was; a `5000`
row is read back on the leader (`GET /v1/orders/{orderId}`); a `5001` row was
not accepted and may be resent.

**Withdrawal during an incident.** The venue may withdraw either route venue
wide during an incident, or for your member alone as a Rules measure with an
objective ground and a case reference, notified to you by mail with the
section 36 appeal route; a withdrawn route answers exactly as before revision
3.74, HTTP `404` with no body on REST and `2013` "unknown op" on WebSocket,
and your single amendments and cancels, cancel-all and cancel on disconnect
stay.

**Re-certification.** Adopting the batch amendment or cancellation routes
adds a surface outside your certified scope and is a material update of your
trading system: re-certify with the Conformance Test Plan's rows X-07 to X-11
before you use them on production.

**Not offered over FIX.** FIX 4.4 has no message that amends or cancels a list
of orders by id; a FIX member sends 35=G and 35=F per order, or uses these
routes on a REST or WebSocket connection alongside.

## 6d. Client identity for transaction reporting (RTS 22)

PM MTF Ltd, as the operator of this MTF, owes a transaction report under MiFIR
Article 26(5) for every transaction executed through its systems by a member
that is not itself a MiFID II investment firm. Where such a member's trade
carries client flow, the venue's report must identify the underlying client per
RTS 22 Annex I Table 2. This section is the surface on which members supply
that identification, keyed by the same `clientUid` they already stamp on every
order. On commodity-derivative markets the venue's own report files your
order's `reg.riskReducing` as RTS 22 field 64 and defaults it to false when the
order carried no key (revision 3.70, section 6); on every other market the key
is recorded and not reported.

Who this applies to:

- **Members that are MiFID II investment firms** report their own transactions.
  The venue does not report for them, they never need to submit identity data,
  and nothing in this section applies to them.
- **Members that are not MiFID II investment firms** are reported for BY THE
  VENUE. For those members, every reportable trade side that carries a
  `clientUid` needs an identity record on file so the venue's report can
  identify the buyer or seller behind it.
- **Market makers** in that category can register their own LEI against their
  house `clientUid` (the configured house identifier applied when they omit
  `clientUid` on order entry), so own-account sides resolve too.

**Trading is never blocked on identity data.** Order entry does not read this
store, and no order, quote or RFQ acceptance is ever refused because a
`clientUid` has no identity record. The coupling is temporal instead: the
transaction report is due by close of the FOLLOWING WORKING DAY, and the
missing report below is the tool for chasing the backlog inside that window.

| Endpoint | Description |
|---|---|
| `PUT /v1/clients/{clientUid}/identity` | Create or update the identity record for one `clientUid`. An upsert: the same route is the update mechanism, and every PUT appends an immutable audit revision (immutable until the retention floor, when the identifying fields of a natural person are removed from every revision; "Retention" below). Requires a trading-capable credential; a READ_ONLY (drop copy) key is refused with `1005` |
| `GET /v1/clients/{clientUid}/identity` | The current record; `2031` (HTTP 404) when none exists or after the venue pseudonymised the record ("Retention", below) |
| `GET /v1/clients/identity/missing?from=&to=&limit=` | Distinct `clientUid`s of yours that traded on MiFIR-scope markets (perpetual and barrier markets, revision 3.52; the swap markets from the date venue operations announce, revision 3.70) in the window with no identity record on file. A member whose licensing profile records that it reports its own transactions (a MiFID II investment firm) always receives an empty `clients` array (revision 3.70): the venue chases none of its clients |

All three routes are authenticated and HMAC-signed like every private route
(section 3) and are metered on the read bucket: the PUT is reference data, not
order entry, so it never consumes an order-budget unit. The store is served
from the venue's history store; a deployment without it answers
`5004` with HTTP 503 on all three routes. The identity store sits outside the
engine sequence, so the envelope `asOfSeq` is `0` on the PUT and on the
single-record GET; the missing report's envelope `asOfSeq` is the projection
watermark (see below).

### Submitting a record, `PUT /v1/clients/{clientUid}/identity`

The path `clientUid` is the identifier exactly as you stamp it on orders (1 to
64 characters; one that begins or ends with whitespace rejects with `2013` on
the PUT and on the GET since revision 3.72, section 2). The body is STRICT like order entry: an unknown JSON field
rejects with `2017` and is never silently discarded.

| Field | Rules |
|---|---|
| `type` | `"NATURAL_PERSON"` or `"LEGAL_ENTITY"`. Required |
| `countryCode` | ISO 3166-1 alpha-2, uppercase. Required for natural persons (the nationality that selects the Annex II identifier priority); optional for legal entities |
| `identifierPriority` | Optional integer 1 to 3: the Annex II priority rank the supplied national identifier holds for that country. Since revision 3.53 it defaults to the rank the RTS 22 Annex II table gives that identifier for the country, and when sent it must agree with the table (see "The Annex II identifier table" below) |
| `nationalIdType` | Your scheme label for the national identifier, 1 to 32 chars. Since revision 3.53 the label must be one the RTS 22 Annex II table designates for `countryCode` (canonical labels and aliases per country, below); `NATIONAL_ID` is accepted as the generic alias of the country's first national number, `PASSPORT` where the country lists the passport, `TAX_ID` where it lists a tax number. Required when `nationalId` is set, and only then |
| `nationalId` | The national identifier itself, 1 to 64 chars. Natural persons only. Since revision 3.70 the value is checked on both rollout stages before the Annex II table: no control or formatting character, no leading or trailing whitespace, and never a leading `=`, `+`, `-` or `@` (a national identifier never starts with one; the value is filed verbatim as the position holder identifier of the venue's Art 58 report), refused with 2030 naming the rule. A `+` or `-` inside the value (the Finnish personal identity code, the Swedish personal identity number) is accepted as before |
| `firstName`, `surname` | 1 to 140 chars each. Required for natural persons |
| `dateOfBirth` | `YYYYMMDD`. Required for natural persons |
| `lei` | ISO 17442 LEI, checksum validated. Required for legal entities |
| `positionHolderCategory` | Revision 3.70. The client's MiFID II Article 58(4) position-holder category: `INVESTMENT_FIRM_OR_CREDIT_INSTITUTION`, `INVESTMENT_FUND`, `OTHER_FINANCIAL_INSTITUTION`, `COMMERCIAL_UNDERTAKING` or `EMISSIONS_OPERATOR`; any other value rejects with `2030`. Optional on both client types, and required in practice for every `clientUid` that holds a position on a market flagged as a commodity derivative (the XAU, WTI and BRENT perpetuals and their knock-out markets, and since revision 3.75 on the sandbox the XAG perpetual and its knock-out market): it feeds the venue's weekly Article 58(1)(a) classification of position holders; a record without it is still reported in the venue's daily Article 58(1)(b) position report and is chased by venue operations for the weekly report. Omitted on a PUT = cleared |
| `ultimateParentLei` | Revision 3.70. ISO 17442 LEI of the client's ultimate parent entity, checksum validated (`2030` otherwise), for the same daily position report; equal to `lei` means the client is its own parent. Optional on both client types; omitted on a PUT = cleared |

For a natural person who holds more than one nationality, choose `countryCode`
as RTS 22 Article 6(3) and the ESMA transaction reporting guidelines
(ESMA/2016/1452, section 5.5.1) direct: an EEA nationality over a non-EEA one,
and among several nationalities of the same kind the ISO 3166-1 alpha-2 code
that sorts first (a German and United States national is `DE`, a Brazilian and
United States national is `BR`, a British and German national is `DE` because
the United Kingdom is outside the EEA); the venue evaluates the identifier
against that country's row and never re-derives the choice from data it does
not hold.

Cross-field rules: `lei` is accepted on `LEGAL_ENTITY` only; `dateOfBirth`,
`nationalId` and `nationalIdType` on `NATURAL_PERSON` only;
`positionHolderCategory` and `ultimateParentLei` on both, checked before the
type rules. Any validation failure rejects with `2030`, and the `msg` names the
offending field. Trading is never blocked on either Article 58 field.

Example, a natural person with a designated national identifier (PESEL is the
first-priority identifier for Poland, so `identifierPriority` resolves to 1 and
the scheme to `NIDN`):

```json
PUT /v1/clients/CLIENT-A/identity
{
  "type": "NATURAL_PERSON",
  "countryCode": "PL",
  "nationalIdType": "PESEL",
  "nationalId": "44051401359",
  "firstName": "Jan",
  "surname": "Kowalski",
  "dateOfBirth": "19440514"
}
```

```json
{
  "code": 0, "msg": "ok",
  "data": {
    "clientUid": "CLIENT-A",
    "type": "NATURAL_PERSON",
    "countryCode": "PL",
    "identifierPriority": 1,
    "nationalIdType": "PESEL",
    "nationalId": "44051401359",
    "firstName": "Jan",
    "surname": "Kowalski",
    "dateOfBirth": "19440514",
    "lei": null,
    "positionHolderCategory": null,
    "ultimateParentLei": null,
    "mifirId": "44051401359",
    "mifirIdSource": "NATIONAL_ID",
    "mifirIdScheme": "NIDN",
    "revision": 1,
    "updatedAt": 1785000000123
  },
  "asOfSeq": 0, "ts": 1785000000123
}
```

### The Annex II identifier table (revision 3.53)

RTS 22 Annex II binds whoever populates the buyer and seller identification,
and for the legs the venue reports that is PM MTF Ltd, so the venue now checks
a natural person's identifier choice against the per-country priority table
and records the ISO 20022 scheme it reports the identifier under
(`mifirIdScheme`: `LEI`, `NIDN` for a national identification number, `CCPT`
for a passport, `CONCAT`). The table, as printed in the Annex, with the labels
the venue accepts as `nationalIdType` (aliases in brackets):

| Country | 1st priority | 2nd | 3rd |
|---|---|---|---|
| AT, DE, FR, HU, IE, LU | CONCAT | | |
| BE | `NATIONAL_NUMBER` (NRN, RIJKSREGISTERNUMMER) | CONCAT | |
| BG | `PERSONAL_NUMBER` (EGN) | CONCAT | |
| CY | `PASSPORT` | CONCAT | |
| CZ, SK | `BIRTH_NUMBER` (RODNE_CISLO) | `PASSPORT` | CONCAT |
| DK | `CPR` | CONCAT | |
| EE | `PERSONAL_CODE` (ISIKUKOOD) | | |
| ES | `TAX_ID` (DNI, NIE, NIF) | | |
| FI | `PERSONAL_IDENTITY_CODE` (HETU) | CONCAT | |
| GB | `NATIONAL_INSURANCE` (NINO) | CONCAT | |
| GR | `DSS` | CONCAT | |
| HR | `OIB` | CONCAT | |
| IS | `KENNITALA` | | |
| IT | `FISCAL_CODE` (CODICE_FISCALE, TAX_ID) | | |
| LI | `PASSPORT` | `IDENTITY_CARD` | CONCAT |
| LT | `PERSONAL_CODE` (ASMENS_KODAS) | `PASSPORT` | CONCAT |
| LV | `PERSONAL_CODE` (PERSONAS_KODS) | CONCAT | |
| MT | `NATIONAL_IDENTIFICATION_NUMBER` (ID_CARD) | `PASSPORT` | |
| NL | `PASSPORT` | `IDENTITY_CARD` | CONCAT |
| NO | `FOEDSELSNUMMER` | CONCAT | |
| PL | `PESEL` | `TAX_ID` (NIP) | |
| PT | `TAX_ID` (NIF) | `PASSPORT` | CONCAT |
| RO | `CNP` | `PASSPORT` | CONCAT |
| SE | `PERSONAL_IDENTITY_NUMBER` (PERSONNUMMER) | CONCAT | |
| SI | `EMSO` | CONCAT | |
| all other countries | `PASSPORT` | CONCAT | |

`NATIONAL_ID` is accepted as the generic alias of the country's first
national number WHERE THE COUNTRY'S ROW LISTS ONE; for a country whose row
lists no national number (AT, CY, DE, FR, HU, IE, LU, and every country not in
the table, whose row is passport then CONCAT) `NATIONAL_ID` is not an Annex II
identifier and draws the same warning, and later the same `2030`, as any other
undesignated label: send `PASSPORT` or omit the national identifier so CONCAT
is derived. Labels are case-insensitive; spaces and hyphens read as
underscores. Where the identifier has a check character (IT fiscal code, ES DNI
and NIE, PT NIF, PL PESEL, EE and LT personal codes) the venue verifies it;
the other national NUMBERS (BE, BG, CZ, DK, FI, GB, GR, HR, IS, LV, NO, RO,
SE, SI, SK, and the PL tax number) are checked for shape (digit count, or the
country's pattern); passports, identity cards and the national identification
numbers of LI, MT and NL are not format-checked.

The GB row applies to UK nationals as the Annex prints it (National Insurance
number, then CONCAT; revision 3.70 records the venue's decision): send
`NATIONAL_INSURANCE` (alias `NINO`), or omit the national identifier so CONCAT is
derived, which for GB is the second priority and so carries the lower-priority
warning. A `PASSPORT` label for `GB` is not an Annex II identifier and draws the
same warning, and later the same `2030`, as any other undesignated label.

What the check does, per submission:

- A **warning** (never a rejection) when a lower-priority identifier is
  supplied while the country lists a higher one (for example CONCAT for PT,
  whose first priority is the tax number, or a passport for LT).
- A **contradiction** when the label is not one the country designates, when
  `identifierPriority` disagrees with the table, when the identifier fails the
  country's format check, or when no `nationalId` is supplied for a country
  whose row lists no CONCAT at all (EE, ES, IS, IT, MT, PL).
- The rollout runs in two stages and the stage in force is fixed per environment:
  on production the **ENFORCE** stage is in force from the venue's first day; on
  the sandbox the **WARN** stage (in force since revision 3.53) ends on Monday
  2026-09-21 and the ENFORCE stage applies from Tuesday 2026-09-22 (announced on
  2026-09-10 on the members Slack channel and the portal notice, seven clear
  Business Days). In the WARN stage a contradiction is stored as submitted and
  reported in a `warnings` array on the PUT echo (omitted when empty and never on
  GET). In the ENFORCE stage a contradiction rejects the PUT with `2030`, the
  `msg` names the contradiction and nothing is stored; a plain warning (a
  lower-priority identifier the country lists) still stores and is still
  reported. Sandbox members are asked to clear their warnings before Tuesday
  2026-09-22. Records stored with a contradiction before the switch stay on file;
  the venue's compliance function may ask you to resubmit them.
- `identifierPriority` may be omitted: the venue stores the table rank.
- Records stored before revision 3.53 keep what was submitted; the venue
  classifies their scheme itself and reports the exceptions to its own
  compliance function, and may ask a member to resubmit a record.

Example: a passport for a German national is a contradiction (DE lists CONCAT
only). In the WARN stage (sandbox until 2026-09-21) the record stores with:

```json
{
  "code": 0, "msg": "ok",
  "data": {
    "clientUid": "CLIENT-D",
    "type": "NATURAL_PERSON",
    "countryCode": "DE",
    "identifierPriority": null,
    "nationalIdType": "PASSPORT",
    "nationalId": "C01X00T47",
    "mifirId": "C01X00T47",
    "mifirIdSource": "NATIONAL_ID",
    "mifirIdScheme": "CCPT",
    "revision": 1,
    "updatedAt": 1785000000123,
    "warnings": [
      "nationalIdType 'PASSPORT' is not an Annex II identifier for DE (designated: CONCAT)"
    ]
  },
  "asOfSeq": 0, "ts": 1785000000123
}
```

- **`mifirId`, `mifirIdSource` and `mifirIdScheme`.** `mifirId` is the
  identifier the venue's transaction report will carry for this client,
  resolved at submission time: the `lei` (`mifirIdSource: "LEI"`), the
  `nationalId` (`"NATIONAL_ID"`), or the derived CONCAT code (`"CONCAT"`,
  below). It is stored resolved, so what the GET shows is exactly what the
  venue will report. `mifirIdScheme` (revision 3.53) is the ISO 20022 scheme it
  is reported under: `LEI`, `NIDN` (national identification number), `CCPT`
  (passport) or `CONCAT`; empty on records stored before the venue classified
  schemes, until the venue's re-validation fills it.
- **`revision`.** A monotonic integer per `clientUid`, starting at 1. Every PUT
  both replaces the current record and appends an immutable audit revision of
  the full record, so a corrected report can show what was on file at any
  earlier revision (immutable until the retention floor, when the identifying
  fields of a natural person are removed from every revision; "Retention",
  below). To fix a field, resubmit the whole record.
- **CONCAT derivation.** When a natural person has no `nationalId`, the venue
  derives the RTS 22 Annex II fallback identifier exactly as the ESMA
  transaction reporting guidelines (ESMA/2016/1452, section 5.5.1) prescribe:
  `countryCode` + `dateOfBirth` + the first five letters of the first name +
  the first five of the surname, each name part padded to five with `#`. The
  venue applies the four ESMA steps itself, so submit the name as you hold it:
  (1) the full first name and surname as submitted; (2) titles are removed
  from both fields (`Dr`, `Prof`, `Mr`, `Mrs`, `Ms`, `Sir`, `Madame` and the
  rest of the ESMA list, case-insensitive, with or without a trailing dot);
  (3) the listed surname prefixes are removed when they stand as separate
  leading words (`van`, `van der`, `von`, `de`, `de la`, `di`, `da`, `dos`,
  `du`, `mac`, `mc`, `o`, `ó`, `ní` and the rest of the ESMA list, longest
  match first; a prefix written into the name is kept, so `McDonald`,
  `O'Brian` and `Vandenberg` keep it; a listed prefix or title that is the
  WHOLE field is the name and stays, `Le` derives `LE###`), and only the FIRST first name is used
  (`Jon Ian` derives `JON##`; a hyphenated first name is one name, `Amy-Ally`
  derives `AMYAL`); (4) the ESMA transliteration table is applied character by
  character and the result uppercased: every character in the table folds to
  its listed Latin letter (`Ø` to `O`, `ß` to `S`, `Æ` to `A`, `Þ` to `T`,
  `Œ` to `O`, ...), and every character outside the table, apostrophes,
  hyphens, spaces, digits and letters of other scripts included, is removed
  rather than approximated. The example above submitted without `nationalId`
  would store `"mifirId": "DE19840217JURGEMULLE"` with
  `"mifirIdSource": "CONCAT"` (`Jürgen` folds to `JURGE`, `Müller` to
  `MULLE`); ESMA's own examples derive the same way (`Ludwig Van der Rohe`,
  HU, 1981-02-14 gives `HU19810214LUDWIROHE#`; `Amy-Ally Garção de
  Magalhães`, PT, 1990-05-17 gives `PT19900517AMYALGARCA`). A name whose
  first name or surname folds to no Latin letter at all (another script, or
  nothing left after the titles and prefixes) would derive a `#####` part
  that collides across clients, so the venue refuses it with `2030` instead
  of storing it: resubmit `firstName` and `surname` in transliterated Latin
  form as the ESMA guidelines expect, or supply `nationalId` and
  `nationalIdType`, which take precedence over CONCAT anyway.

### Reading a record, `GET /v1/clients/{clientUid}/identity`

Returns the current record in exactly the shape above. A `clientUid` with no
record answers `2031` (HTTP 404), and so does one whose record the venue
pseudonymised at the retention floor ("Retention", below). A READ_ONLY
credential may call it, so a back-office reporting process can verify coverage
without being able to trade.

### The missing report, `GET /v1/clients/identity/missing`

The chase list for the reporting window: every distinct `clientUid` of your
member that traded on MiFIR-scope markets (the perpetual, the barrier since revision 3.52 and the swap markets from the date venue operations announce under revision 3.70) inside the window and has
no identity record on file (a record the venue pseudonymised at the retention
floor counts as none, so a client that trades again is chased again). Since
revision 3.70 a member whose licensing profile records that it reports its own
transactions under MiFIR Article 26 (a MiFID II investment firm) receives an
empty `clients` array with the same `from`, `to` and `asOfSeq`: the venue files
no report for such a member and chases none of its clients; the same rule
removes its rows from the venue's own chase lists.

| Param | Rules |
|---|---|
| `from` | Unix ms, inclusive. Default: `to` minus 48 hours |
| `to` | Unix ms, exclusive. Default: now |
| `limit` | 1 to 1000, default 500 |

```json
{
  "code": 0, "msg": "ok",
  "data": {
    "from": 1784913600000,
    "to": 1785000000000,
    "clients": [
      { "clientUid": "CLIENT-B", "tradeSides": 7,
        "firstTradeTs": 1784920000123, "lastTradeTs": 1784999911456 }
    ]
  },
  "asOfSeq": 104870, "ts": 1785000000456
}
```

- Rows are ordered oldest first trade first, so the entries closest to the
  reporting deadline come first. `tradeSides` counts your trade sides for that
  identifier inside the window; `firstTradeTs` and `lastTradeTs` bound them.
- Spot activity does not appear: the report covers the venue's MiFIR reporting
  scope, which is the perpetual AND the barrier markets (a barrier contract is a
  derivative traded on the MTF and its opening and closing trades are
  transaction-reportable; before revision 3.52 barrier trades were left out and
  the venue held no chase list for them). Barrier `clientUid`s therefore appear
  here from revision 3.52 on: they were always reportable, the report simply did
  not list them.
- The default window is the last 48 hours. Pass `from` yourself when you need
  to sweep further back, for example on the working day after a weekend.
- **The envelope `asOfSeq` is the projection watermark**: the report sees
  trades only up to that sequence, so a trade acked a moment ago may not be
  visible in it yet. For an end-of-day sweep this does not matter; for a
  trade-then-poll integration test it does.
- An empty `clients` array means every reportable side in the window is
  covered.

### Error codes and scope

New registry codes for this surface: `2030 CLIENT_IDENTITY_INVALID` (HTTP 400,
the `msg` names the offending field) and `2031 CLIENT_IDENTITY_NOT_FOUND`
(HTTP 404). Also reachable here: `1005` (READ_ONLY credential on the PUT),
`2017` (unknown JSON field in the body), `2013` (since revision 3.72, a path
identifier that begins or ends with whitespace, section 2) and `5004` (the
venue's history store is not configured on this deployment).

**What the venue does with the data.** The record exists solely for the venue's
own regulatory reporting: it populates buyer and seller identification in the
transaction reports the venue owes under MiFIR Article 26(5) and, where a
surveillance alert concerns the client, names the person in the suspicious
transaction and order report (STOR) the venue files with CySEC. It is never read
by matching, margin or any risk path, and it is disclosed only through that
reporting, never to another member.

**Transports.** REST only. There is no WebSocket op and no FIX surface for
identity data.

### Trader identity, `PUT` and `GET /v1/trader-ids/{traderId}/identity` (revision 3.53)

For the legs the venue reports under Article 26(5), RTS 22 fields 57 to 60
(investment decision within firm, execution within firm, and their branch
countries) must carry the natural person under the Annex II identifier scheme,
or the algorithm code, never a house code. This route resolves a Trader ID you
stamp on orders (`reg.traderId`, and the codes you send as
`reg.executionDecisionId` and `reg.investmentDecisionId`) to a person or an
algorithm. MiFID II firm members report their own fields 57 to 60 and never
need it.

| Endpoint | Description |
|---|---|
| `PUT /v1/trader-ids/{traderId}/identity` | Create or update the identity behind one Trader ID; every PUT appends an immutable audit revision (immutable until the retention floor, "Retention" below). Trading-capable credential (`1005` otherwise). The path id is 1 to 64 characters of `A-Z a-z 0-9 . _ -` (`2030` otherwise; one that begins or ends with whitespace rejects with `2013` since revision 3.72, on the PUT and on the GET, section 2); it need not be a registered Authorised User (decision codes may name persons who never enter orders) |
| `GET /v1/trader-ids/{traderId}/identity` | The current record; `2031` (HTTP 404) when none, or after the venue pseudonymised the record ("Retention", below) |

Body (STRICT, unknown field `2017`, validation `2030`):

| Field | Rules |
|---|---|
| `kind` | `"PERSON"` or `"ALGO"`. Required |
| `countryCode`, `identifierPriority`, `nationalIdType`, `nationalId`, `firstName`, `surname`, `dateOfBirth` | PERSON: exactly the `NATURAL_PERSON` rules of the client identity route, including the Annex II table, the CONCAT derivation and the WARN / ENFORCE stages |
| `branchCountry` | PERSON, required: ISO 3166-1 alpha-2 country of the branch responsible for the person (fields 58 and 60) |
| `algoId` | ALGO, required: 1 to 50 characters of `A-Z a-z 0-9 . _ -`, the code you report the algorithm under; no person field may be sent |

The echo carries `traderId`, `kind`, the submitted fields, `mifirId` (the
person's identifier or the algorithm code), `mifirIdScheme` (`NIDN`, `CCPT`,
`CONCAT` or `ALGO`), `revision`, `updatedAt` and, on the PUT only, `warnings`
as on the client identity route. Where a Trader ID has no identity on file the
venue's reports carry the code as sent, which is correct for MiFID II firm
members and a chase item for everyone else; venue operations may ask you to
file the identity behind a code that appears on your reported legs.

In the venue's RTS 22 transaction reports a code that resolves to a person is
reported as that person (identifier under its scheme, with the branch country
in fields 58 and 60), a code that resolves to an algorithm as the algorithm
code, and an unresolved code as an algorithm code (up to 50 characters). Two
consequences for the orders you send: an execution code you leave out on an
`AOTC` or `MTCH` order is reported as the client's decision (`NORE`), and a
`DEAL` order (dealing on own account) must carry BOTH `reg.investmentDecisionId`
and `reg.executionDecisionId` (or `reg.traderId` for the execution), because
RTS 22 field 57 is mandatory when dealing on own account (ESMA validation rule
CON-570) and no client exists to attribute the execution to; a `DEAL` leg
without them cannot be filed and becomes a chase item.

```json
PUT /v1/trader-ids/TR-7-A1B2C3D4/identity
{
  "kind": "PERSON",
  "countryCode": "PL",
  "nationalIdType": "PESEL",
  "nationalId": "44051401359",
  "firstName": "Jan",
  "surname": "Kowalski",
  "dateOfBirth": "19440514",
  "branchCountry": "CY"
}
```

### Retention (revision 3.70)

The identity records of natural persons are personal data the venue holds for
its transaction reporting obligation, and MiFIR Article 25(2) requires the
venue to keep the data behind its reports for five years. The venue therefore
keeps a natural person's client identity record for five years after you
report that the client relationship ended (the retention floor), and after that
floor (from the day after the fifth anniversary of the reported end; the
anniversary day itself lies inside the period) venue operations may
pseudonymise it: the identifying fields (`firstName`,
`surname`, `dateOfBirth`, `nationalId`, `nationalIdType`, `identifierPriority`,
`lei`) are removed from the current record AND from every stored audit revision,
`mifirId` and `mifirIdSource` read `PSEUDONYMISED`, and the `clientUid`, the
client type, the country, the scheme, the Article 58 fields and the revision
skeleton stay, with one further revision recording the action. A pseudonymised
record answers `2031` on `GET /v1/clients/{clientUid}/identity` exactly as a
record that was never submitted, the client counts as uncovered on the missing
report should it trade again, and a new PUT on the same `clientUid` starts a
new revision on top of the skeleton (the revision number never restarts). The
archived daily copies of the record and its revisions taken before the
pseudonymisation remain on the venue's write-once archive under retention lock
for seven years after each copy, are used only for disaster restore and are
never served to members. The
trader identity route follows the same rule five years after the trader's
authorisation ended, its record answering `2031` afterwards. Legal-entity
records are never pseudonymised (an LEI is not personal data), and the venue's
FILED transaction reports are kept as filed for the MiFIR Article 25(2) period
whatever happens to the identity record behind them. The removal applies to
the venue's live identity record and its audit revisions. The venue's archive
(section 11.4) keeps a daily copy of the identity record taken before the
pseudonymisation under the archive's retention lock, which runs seven years
from the day of that copy, so the identifying fields remain in the archive
until seven years after the last copy taken before the pseudonymisation; the
archive is never served on any route and is read only through the venue's
restore procedure, and the filed transaction reports are kept as filed for
the same reason. The trigger is the end of
the relationship you report to venue operations; there is no member route for
it in this revision.

## 6e. Lots: a per-client lot view (private)

A CFD-style display of an end client's open position as the individual opening
fills, "lots", that built it, with a targeted per-lot close. Lots are a DISPLAY
CONVENTION folded on demand from your immutable fill history: the engine keeps
no lots, and margin, liquidation, funding attribution and the venue's booked
realized PnL remain average-cost per sub-position exactly as sections 6 and 7
describe. The fold changes reporting, never economics.

`GET /v1/lots` is served from the venue's history store (like
`GET /v1/orders/history`), so its envelope `asOfSeq` is the store watermark: a
fill acked a moment ago may not be folded into the view yet. The close route
is order entry, and its envelope `asOfSeq` is the engine sequence of the close
order's ack, exactly as on `POST /v1/orders`; use that value as the watermark
the GET route must reach before the fold reflects the close.

Lots exist on order-book perpetual and spot markets. BARRIER markets are
refused by both routes with `2013`: barrier contracts are bilateral, settle
without closing trades, and have no lots (`GET /v1/barriers` is their view).

**The fold.** Lots exist per (`clientUid`, hedge bucket, instrument), where the
bucket is the netting bucket or the hedge-mode `LONG`/`SHORT` bucket of
section 6. Every fill that INCREASES the bucket's position opens a lot: its
`lotId` is the `tradeId` of the opening fill and its entry price is that fill's
price. Every fill that REDUCES the bucket consumes open lots: lots explicitly
assigned to the reducing order first (the targeted close below), in assignment
order, then the remaining quantity FIFO from the oldest open lot. A fill that
crosses the bucket through zero closes every open lot and opens one new lot in
the opposite direction with the remainder.

**What the numbers mean.** The open lots' `qty` sums exactly to the bucket's
net quantity once `asOfSeq` has passed your last fill. Per-lot `unrealizedPnl`
is measured from the lot's OWN entry price to the mark, while the bucket's
`unrealizedPnl` on `GET /v1/positions/end-clients` is measured from its average
cost, so the two sums agree only until a partial close leaves lots at different
entry prices. From then on they differ by exactly the realized PnL the lot
attribution has taken over the venue's booked figure since the bucket was last
flat, and the difference disappears when the bucket is flat again. Example: lots
of 10 at 100 and 10 at 120 (average 110); the client closes 4 of the first lot
at 130. The closure row shows 4 x 30 = 120 realized, the venue books
4 x 20 = 80, and the remaining lots carry entries worth 1,800 against a booked
cost basis of 1,760, so their `unrealizedPnl` sums 40 below the bucket's.
Realized attribution per lot under FIFO is presentational: it can differ from
the venue's booked average-cost realized PnL on partial closes (so can the
attribution to the lot a targeted close named), which is why this surface
reports OPEN lots only and your ledger remains the average-cost figures. Before revision 3.80 this
paragraph said the `unrealizedPnl` sum was exact.

**The fold is clamped to the live sub-position.** A venue-side close that the
fold cannot attribute to the client (a liquidation on an RFQ-mode market, which
leaves no fill of yours to consume the lot) reduces the client's sub-position
without a closing fill, so the fold trims the OLDEST open lots of the bucket
down to the live sub-position before answering: you never see a lot the venue
has already closed, the close endpoint can never act on one, and the lots'
`qty` keeps summing exactly to the bucket. ADL allocations are attributed from
the per-client ADL record (revision 3.34). Since revision 3.80 a book
liquidation of a hedge-mode client is attributed to its own `LONG` or `SHORT`
bucket from the venue's record of the liquidation order; before it, such a fill
was folded into the netting bucket, so the lot it closed was trimmed here and
the closures feed did not list the close against it.

### Reading lots, `GET /v1/lots?clientUid=&symbol=&positionSide=`

`clientUid` and `symbol` are required. `positionSide` is optional and filters
to one bucket: `NET`, `LONG` or `SHORT`; absent returns the open lots of all
three buckets. Response:

```json
{
  "clientUid": "CLIENT-A",
  "symbol": "BTC-USD-PERP",
  "mark": "69000",
  "settleCcy": "USD",
  "accFunding": "-12.5",
  "openLotsFunding": "-9.2",
  "closedLotsFunding": "-3.3",
  "lots": [
    { "lotId": 990312, "direction": "LONG", "entryPrice": "68000",
      "qty": "0.002", "unrealizedPnl": "2", "accFunding": "-6.1",
      "fundingIntervals": 7, "openedTs": 1785470168892,
      "openClOrdId": "my-open-17", "openOrderId": 774120 },
    { "lotId": 990840, "positionSide": "LONG", "direction": "LONG",
      "entryPrice": "68500", "qty": "0.001", "unrealizedPnl": "0.5",
      "accFunding": "-3.1", "fundingIntervals": 4,
      "openedTs": 1785470201230,
      "openClOrdId": "my-open-18", "openOrderId": 774131 }
  ]
}
```

- Lots are grouped by bucket, `NET` then `LONG` then `SHORT`, and oldest first
  WITHIN each bucket, which is that bucket's FIFO consumption order. The array
  is not globally age-ordered across buckets; filter by `positionSide` for a
  single age-ordered list.
- `lotId` is the `tradeId` of the opening fill, so it is stable, unique and
  correlates with your executions.
- `positionSide` is `LONG` or `SHORT` on hedge-bucket lots and OMITTED, not
  null, on netting-bucket lots, exactly like every other order surface.
- `direction` is the lot's own direction (`LONG` | `SHORT`): in the netting
  bucket lots flip through zero, so a netting lot can be short.
- `qty` is the lot's REMAINING quantity after partial consumption.
- `unrealizedPnl` is at the current `mark`; both are OMITTED, not null, while
  the market has no mark price yet, following the API's usual absent-field
  convention.
- `openClOrdId` and `openOrderId` are YOUR OWN references on the fill that
  opened the lot (revision 3.32): the `clOrdId` you sent on the opening order
  and the venue `orderId` it was accepted as, so a lot is addressable by your
  client-generated id without a mapping table. They are transport
  independent: an order entered over FIX carries its `ClOrdID(11)` into
  `openClOrdId` exactly as a REST `clOrdId` does, which is what venue tag
  `9028` resolves against (section 6e transports, FIX Gateway guide section
  2). Both are OMITTED, not null, where the opening leg has no order of yours
  to name: an order sent without a `clOrdId`, an RFQ acceptance leg, or a
  venue-initiated open.
- **`openClOrdId` is not a unique key.** One order that fills in several
  prints opens ONE LOT PER FILL, and every one of them carries that order's
  reference, so a reference can name several lots. `clOrdId` uniqueness is a
  24 hour window (section 6), so a reference you have reused can also name
  lots from different orders; where the venue has to resolve a reference to
  lots itself (FIX tag `9028`) the MOST RECENT order bearing it wins. Read
  `lotId` for identity and `openClOrdId` for attribution.
- The fold reads at most 100,000 fills per (`clientUid`, `symbol`) scope; a
  scope past that bound is refused with `2013` (HTTP 400) and a message naming
  the bound. Reconcile such a client by positions and executions instead.

**Funding on lots.** Each lot carries `accFunding`, the financing it has been
charged since it opened, in `settleCcy` and signed from the client's side
(negative paid), plus `fundingIntervals`, how many settlements it has been open
across (counting anchors that settled at a zero rate). Funding is 0 on spot,
which never funds.

The venue charges funding on the client **sub-position net**, once per settlement
anchor, never per lot, so the per-lot figure is an **attribution** of a booked
charge and not itself a booked figure. The attribution is exact: each anchor
charges exactly the lots that were open when it settled, and the shares sum to
the charge on the bucket net **to the micro** (the rounding remainder is booked
to the largest lot, the same rule the venue uses to split a member's charge
across its clients). That exactness is against the charge on the bucket net:
the booked `accFunding` of the one client that receives the remainder of
splitting YOUR member charge across your clients can differ from the sum of its
lots by that remainder, which then shows in `closedLotsFunding` (stated since
revision 3.80). Financing follows the quantity actually held, so a partially
closed lot keeps what it incurred while it was larger, and a fully closed lot
takes its financing with it.

That is why the envelope publishes three figures, and you should read all three:

| Field | Meaning |
| --- | --- |
| `accFunding` | **Authoritative.** Lifetime funding the venue BOOKED on this sub-position, over the buckets the response covers. |
| `openLotsFunding` | Sum of `accFunding` over the lots listed. Attribution, not a booked figure. |
| `closedLotsFunding` | The difference: financing already paid by quantity since closed. Non-zero is normal on any client that has ever reduced. |

> **Reconcile client statements against `accFunding`, not against the sum of the
> lots.** The same caution as per-lot realized PnL in the note above: the lot
> figures are a presentation of a booked total, and only the booked total is what
> the venue will settle with you.

The fold is bounded, and the bounds are published so you can predict when a
client crosses one instead of discovering it in production:

| Bound | Value | Scope |
| --- | --- | --- |
| Fills read | 100,000 | per (`clientUid`, `symbol`) |
| Funding settlements attributed | 20,000 | per scope, counted from the first fill the fold covers; about 18 years of an 8 hour grid |
| Attribution steps (settlements x lots open at each) | 5,000,000 | per fold |

Past a bound the request is refused with `2013` (HTTP 400) and a message naming
the bound. `positionSide` narrowing folds ONLY that bucket, so it reduces the
attribution work in proportion to the bucket's share and starts the settlement
count at that bucket's first fill; the work bound binds before the settlements
bound only above roughly 250 average open lots. Past the settlements bound, or
when a single bucket exceeds the work bound on its own, there is NO in-API
remedy: reconcile that client by positions and executions instead, and note
that `POST /v1/lots/{lotId}/close` is refused for such a scope too.

### Reading closures, `GET /v1/lots/closures?clientUid=&symbol=&positionSide=&from=&to=`

The close-attribution feed: every CLOSING fill in the scope, mapped to the exact
lot or lots it consumed by the SAME fold that builds `GET /v1/lots`. The feed
folds WITHOUT the clamp above, so the one close it cannot list is a close the
fold cannot attribute (a liquidation on an RFQ-mode market): the lots view trims
that lot and no closure row names it. `clientUid` and `symbol` are required, `positionSide`
filters to one bucket exactly as above, and `from` and `to` are optional Unix ms
bounds on the closing fill's `ts`. Rows are oldest first, and one closing fill
that consumed several lots appears as one row per lot, in consumption order.

```json
{
  "clientUid": "CLIENT-A",
  "symbol": "BTC-USD-PERP",
  "settleCcy": "USD",
  "closures": [
    { "closeTradeId": 991402, "lotId": 990312, "qty": "0.002",
      "openPrice": "68000", "closePrice": "69100", "realizedPnl": "2.2",
      "openClOrdId": "my-open-17", "closeClOrdId": "my-close-31",
      "flags": "NORMAL", "unsolicited": false, "ts": 1785470880110 },
    { "closeTradeId": 991540, "lotId": 990840, "positionSide": "LONG",
      "qty": "0.001", "openPrice": "68500", "closePrice": "67200",
      "realizedPnl": "-1.3", "openClOrdId": "my-open-18",
      "closeClOrdId": "KX-ADL-991540", "flags": "ADL",
      "unsolicited": true, "ts": 1785471002480 }
  ]
}
```

| Field | Meaning |
| --- | --- |
| `closeTradeId` | The `tradeId` of the closing fill, so a row correlates with your executions. |
| `lotId` | The lot consumed, as `GET /v1/lots` reported it while it was open. |
| `positionSide` | The hedge bucket, `LONG` or `SHORT`; OMITTED, not null, on netting-bucket rows. |
| `qty` | How much of the lot THIS row consumed, decimal base units, not the closing fill's total. |
| `openPrice`, `closePrice` | The lot's entry price and the closing fill's price. |
| `realizedPnl` | `(closePrice - openPrice)` x the LOT's direction x `qty`, in `settleCcy`. FIFO attribution, presentational: the venue books realized PnL average-cost per sub-position, and only that booked figure settles with you. |
| `openClOrdId` | YOUR reference on the order that opened the consumed lot, the same value `GET /v1/lots` carries. |
| `closeClOrdId` | Your reference on the CLOSING order. Where the close was venue-initiated there is no reference of yours, so the venue derives one in its reserved namespace: `KX-ADL-{closeTradeId}` for an auto-deleveraging tear-up and `KX-LQ-{orderId}` for a liquidation close. `KX-` is refused on order entry with `2020`, so a derived reference can never collide with one of yours; do not match it against your own order table. |
| `flags` | The closing fill's trade flags: `NORMAL`, `LIQUIDATION`, `ADL` or `REVERSAL` on the markets that have lots. The set is open, parse defensively. |
| `unsolicited` | `true` exactly when `flags` is `LIQUIDATION`, `ADL` or `REVERSAL`: the venue closed the lot, you did not. |
| `ts` | Unix ms of the closing fill. |

- **This is where a targeted close is verified.** An assignment is advisory and
  never rejects: a close that fills after the lot shrank takes what is left of
  it and sends only the surplus FIFO; a FIX `9020` or `9028` target that names
  no open lot of that client in that bucket sends the whole close FIFO; and an
  IOC that finds no liquidity closes nothing, so the lot stays open. The
  closures feed states which lots were actually consumed, so reconcile the
  intent you sent against what the fold recorded.
- A hedge-mode client's book liquidation is listed against the lot of its own
  `LONG` or `SHORT` bucket since revision 3.80 (before it, the fill was folded
  into the netting bucket and its close was not listed against the bucket's
  lot).
- ADL tear-ups are included (revision 3.34), sourced from the per-client ADL
  record because the ADL trade itself carries no client identifiers, and
  attributed to the correct hedge bucket. A defaulted member's own ADL closures
  are listed here too, sourced from its own per-client `adl` rows (revision 3.70,
  `role` `DEFAULTER`, section 10; in force from the engine sequence venue
  operations announce), in the direction the venue closed each client in, with
  the same derived `KX-ADL-{tradeId}` reference and `unsolicited` true.
- The route folds the same fill history as `GET /v1/lots` and is bounded the
  same way: past a bound it is refused with `2013` naming it, an unknown symbol
  answers `2001`, a barrier symbol `2013`, and a missing `clientUid` or
  `symbol` `2013`. Its envelope `asOfSeq` is the history-store watermark, like
  the lots route.

### Closing one lot, `POST /v1/lots/{lotId}/close`

```json
{ "clientUid": "CLIENT-A", "symbol": "BTC-USD-PERP", "reduceOnly": false, "clOrdId": "close-lot-1" }
```

Closes a specific open lot: the venue submits a MARKET IOC order on the
opposite side of the lot for the lot's remaining quantity, tagged with the
lot's hedge bucket (`positionSide` for a `LONG`/`SHORT` lot, untagged for a
netting lot), and records an ASSIGNMENT of that order to that lot, so the fold
consumes THAT lot first rather than the FIFO-oldest one. The response is
`{ "lot": {...}, "order": {...} }`: the lot as the fold saw it at request time
and the ordinary place-order response data for the submitted close, executions
included. Since revision 3.26 the body also takes an optional `reg` object,
same shape and rules as `POST /v1/orders`: a lot close IS order entry, so a
member at ENFORCE-stage capacity enforcement (section 6f) must carry the
capacity here too or the close rejects `2033`; the natural value is `AOTC` (or
`MTCH` under a matched principal model), and ungated members can omit the
object with no change in behaviour.

- The body is strict like order entry (`2017` on an unknown field);
  `clientUid` and `symbol` are required, `clOrdId` is your optional reference
  with the usual rules, the reserved `KX-` prefix rejected with `2020`
  included.
- A `lotId` that is not an OPEN lot of that `clientUid` and `symbol`, never
  opened, already fully closed, closed venue-side (the clamp above), or
  belonging to a different client, answers `2032` (HTTP 404) and nothing is
  submitted.
- An RFQ-mode market is refused with `2022` before anything is submitted: the
  close submits a book order and an RFQ market has no book. Reduce an RFQ-mode
  position through the RFQ flow instead.
- One close per lot at a time: a second close of the same `lotId` arriving
  while the first is still in flight is refused with `2013` and the message
  "a close for this lot is already in flight". Retry after the first close's
  outcome is visible.
- `clOrdId` reuse mirrors `POST /v1/orders` exactly: reusing a reference with
  a DIFFERENT payload rejects `2009` (envelope-level, HTTP 400), and reusing
  it with the identical payload returns the ORIGINAL order with
  `duplicate: true`, places nothing new and records NO lot assignment, so a
  restatement can never bind an old order to a new lot. The payload of a lot
  close is built from the lot as it stands when the request arrives, so a retry
  with the same `clOrdId` after a lost response is identical only while the
  first close has changed nothing: once it filled the whole lot the retry
  answers `2032` (the lot is gone), once it filled part of it `2009` (the
  remaining quantity, and with it the payload, changed), and while the history
  store has not yet folded the first close it can still answer
  `duplicate: true`. Read your executions or the closures feed before you
  retry (stated since revision 3.80).
- The assignment record is best effort: if the venue's history store cannot
  record it after the close order already executed, the request still answers
  with the order's true outcome and the lot view degrades to FIFO consumption
  for that close, exactly as for an unfilled IOC. The venue logs the loss;
  economics are unaffected.
- **This is order entry.** The close passes the same admission ladder as
  `POST /v1/orders`: it requires a trading-capable credential (`1005` on a
  READ_ONLY key), consumes one order-budget unit, counts under the
  order-to-trade regime, and its order can reject exactly as any market IOC
  can (`2007` with an empty opposite side, for example). A rejected or
  unfilled close leaves the lot open.
- **`reduceOnly` is applied only when you send it, and the default is false,
  deliberately.** The reduce-only budget is MEMBER-level, never client-level: a
  client-level close while your member aggregate leans the other way rejects
  with `2008` even though the client genuinely holds the lot, and on a
  perfectly netted member book (member net exactly zero) EVERY reduce-only
  order on that instrument is refused. **Read the reduce-only warning in
  section 6 before you set this flag on a lot close**; it is the full statement
  of the rule and it is the reason this default is false. Without the flag the
  close is a plain order under normal margin admission; the corner it opens is
  the documented race below. Send `reduceOnly: true` only when you know the
  MEMBER position covers the close and you want the venue-side guarantee that
  the order can only ever reduce. A close without the flag is an order like
  any other for the funding catch-up fence too: while a GRID market's
  settlement is more than 60 s overdue it is refused with `3014` (section 7,
  "Catch-up fence"); retry it once the catch-up has landed, normally seconds.
- **Dust: a client sub-position worth less than `minNotional` cannot be closed
  by a plain client-level order.** The `minNotional` floor exempts genuinely
  flagged reduce-only orders, but a client-level close is sent WITHOUT the
  flag (previous bullet), so a residual below the floor, the normal residue of
  partial fills on the `qtyStep` grid, rejects `2028` on every plain closing
  order, including this endpoint. The client-attributed escape is to enlarge
  then close the whole position, and the enlarging order must be sized by
  aligning `minNotional / mark` UP to the `qtyStep` grid: aligning down lands
  back under the floor and is refused by the very rule it is escaping. Where
  the MEMBER book covers it, a member-level `reduceOnly` order closes the dust
  directly, exempt from the floor, at the cost of client attribution.
- **Races are resolved by the fills, not the assignment.** The lot is resolved
  and sized at request time; an in-flight fill can consume it between your
  read and the close. The assignment records intent only: the fold honors it
  through the fills the close order actually produces, so an IOC that finds no
  liquidity simply leaves the lot open, and a close that fills after the lot
  shrank folds against what is left, FIFO for any surplus.

**Transports.** The lot VIEWS (`GET /v1/lots` and `GET /v1/lots/closures`) are
REST only. The TARGETED close is available on REST (this endpoint) and, since
revision 3.35, on FIX: venue tag `9020` (TargetLotID) on a NewOrderSingle names
the lot to close, with your side supplying the closing direction, quantity and
hedge bucket itself, because FIX does not derive them from the lot (FIX Gateway
guide, section 2). Since revision 3.36 FIX can also address the close by YOUR
OWN opening order reference instead of the venue lot id: venue tag `9028`
(TargetOpenClOrdID) carries the `clOrdId` you sent on the opening order, and
the venue resolves it to the lot or lots that order opened, oldest fill first,
so a FIX-only integration closes what it opened without ever reading
`GET /v1/lots`. Each of those lots is assigned at its full opening size and the
fold takes only what is left of it, so consecutive partial closes of the same
order walk its lots oldest first, and only quantity beyond the order's last
open lot goes FIFO (revision 3.80; before it, the close was allocated over the
fills' original sizes, so a second partial close of an order that filled in
several prints could send part of its quantity FIFO to the client's oldest
lot). For a partial close that must land on one specific lot, `9020` with an
`OrderQty(38)` no larger than that lot's current `qty` is the exact form. On FIX an unknown or already-closed target degrades that
close to FIFO rather than rejecting; verify against the closures feed. There is
no WebSocket op. The close order itself reports on every transport like any
other order (drop copy included).

## 6f. Trading capacity and IFR flow statements (RegData)

Venue-attributable data only, aggregated by the member across venues, not
regulatory or legal advice. That sentence rides every statement and page this
section describes, and it is the frame to read the whole section in: the venue
gives you the inputs your own prudential monitoring under the Investment Firms
Regulation (IFR, Regulation (EU) 2019/2033) needs from THIS venue, and computes
no requirement for you.

**Trading capacity on orders: venue semantics, stated exactly.** Every order,
RFQ creation and RFQ quote can carry `reg.tradingCapacity`. The field is
spelled in the MiFIR vocabulary but it is NOT MiFIR field 29: it declares the
LEGAL NAME OF EXECUTION under the venue's semantics, and your RTS 22 report
label derives from your configured per-member mapping, never the other way
around. The venue semantics are:

- `DEAL`: dealing on own account.
- `MTCH`: execution in your OWN NAME for a client account, INCLUDING
  commission business (Finanzkommissionsgeschaeft), regardless of how your
  home regime labels it in transaction reports. A commission broker whose
  home practice reports AOTC still tags `MTCH` on the wire; the venue
  configures your MTCH-to-AOTC report mapping during onboarding, in the same
  step as your licensing profile and capacity-tagging attestation. That
  MTCH-to-AOTC mapping is the only report label the venue ever configures, and
  only where the venue's compliance function has so determined on its one
  published test; `DEAL` and `AOTC` always report as themselves, and a member
  that reports its own transactions under MiFIR Art. 26 carries no label
  because the venue does not file its legs.
- `AOTC`: agency in the name of the identified client.

Prudential bucketing (K-DTF and K-COH) keys on these venue semantics: own-name
execution accrues to dealing on own account (IFR Article 4(1)(33)), whatever
your report label says. On FIX the value rides venue tag 9021, and standard
`OrderCapacity(528)` is also accepted for gated members with the mapping `P`
and `G` to `DEAL`, `R` to `MTCH`, `A` to `AOTC`; tag 9021 is the canonical
carrier, an unmappable 528 value is refused with 2033 at ENFORCE, and when
both tags are sent they must agree. The value is recorded verbatim on the
order's regulatory record and never normalized or rewritten.

**Staged enforcement.** The venue operator records, per member, which
capacities the member's licence permits, and stages enforcement per member:
OFF (the default: the value is recorded and never validated, exactly the
behaviour before this revision), WARN (violations are counted venue-side and
reviewed with you, orders are NOT rejected), ENFORCE (a violating order rejects
with code 2033 before it reaches the matching engine). Under ENFORCE the field
is required, the value must be one of the three codes exactly and permitted by
your profile, `DEAL` requires `clientUid` absent or your house reference (own
account flow rides the house account), and `MTCH` and `AOTC` require a real
end-client `clientUid`. Margin previews are reads and are never capacity gated.
Your current enforcement stage and permitted set are shown on the member portal
Regulatory tab.

**Daily flow statements.** The venue aggregates, per member per UTC day (00:00
boundary), the executed value of your fills in the settlement currency, split
by the capacity each order carried: `DEAL` and `MTCH` flow accrues to dealing
on own account (the K-DTF input of IFR Article 33), `AOTC` flow to client
orders handled (the K-COH input of Article 20), cash (spot markets) and
derivatives (perpetual and barrier markets) separately. Fills whose capacity
the venue could not recognise (absent, or recorded before enforcement with a
nonconforming value) count as UNKNOWN, are excluded from both measures, and are
visible as the capacity coverage percentage every statement carries; historical
records are never reclassified. The portal Regulatory tab shows the daily
series, the rolling measures (arithmetic mean over the previous 9 months
excluding the most recent 3 for K-DTF, previous 6 excluding 3 for K-COH, all
calendar days, zero-filled, or Business Days where the venue has put a calendar
in force, below) with the venue-attributable figure after the
Article 15(2) coefficients, and downloads: the daily series as CSV and the
monthly IFR data annex (v3, with a detached Ed25519 signature since revision
3.31; since revision 3.70 the signing-key read names the key by `keyId` and the
annex and its `.sig` download carry `X-KronosX-Annex-Key-Id` beside the
signature header, Member Technical Handbook section 12a), which records the
parameter version,
engine incarnation and projection watermark it was generated from, so an annex
regenerates byte-identical and any figure can be re-derived. Every coefficient
and threshold lives in a venue parameter store with an effective date and a
legal citation; barrier contracts enter the derivatives flow at their canonical
notional (quantity times entry price at the open, quantity times close price on
an early close print). A knock-out, take-profit, expiry, tear-up or final
settlement termination is a fee-free BARRIERSETTLE ledger leg that prints no
trade and enters no flow measure; an early close prints a second trade that
does (revision 3.70; the annex states it as `barrierTreatment`). Since revision
3.70 the annex methodology block also states the replacement-cost treatment of
the venue's pool-facing exposure snapshot (`rcTreatment`): RC is your negative
unrealised PnL from cost basis at the venue mark (IFR Article 28(1)(a) current
market value; no variation is settled before close-out), and your own K-TCD
uses the mirror, your positive unrealised PnL against the pool. The rolling
means divide by every calendar day unless the venue puts a Business Day
calendar in force (Rulebook V1.2: a day other than Saturday or Sunday on which
commercial banks in Cyprus are open for general business): then the annex says
so (`calendarMode`, and per measure the `calendarHolidaysApplied` line), the
means divide by Business Days, and the flow of a non-Business Day is
attributed to the next Business Day, a venue methodology rather than an IFR
rule; the daily series stays per UTC calendar day so you can re-derive a mean
under your own home calendar. Averaging calendar (revision 3.70, confirmed by
venue compliance on 10 September 2026): the calendar in force is every
calendar day (`ALL_DAYS`), so the rolling figures are the arithmetic mean over
EVERY calendar day of their window at the 00:00 UTC boundary, a day without
fills counting as zero. The venue lists no non-trading day, so no day is
excluded and no flow is reattributed to another day; this is the LOWER of the
two readings against a business-day denominator. Which denominator your own
K-factor computation must use is a matter for you and your competent
authority; a member on a business-day basis rebases exactly from the daily
series CSV (up to 400 days, which covers the whole K-DTF window). A change of
basis is a dated parameter row naming the Business Day calendar
(`BUSINESS_DAYS_CY`), never an edit, and this document is the notice of the
mechanism: the calendar resolves once per measurement and applies to the whole
window, the `days` column of the rolling rows is the divisor and reads
Business Days under that calendar, the `calendarMode` and
`calendarHolidaysApplied` header lines (the bank holidays applied, listed per
measure) appear only on an annex measured on the Business Day calendar, and an
annex already generated never changes bytes when the venue switches, because
its parameter version is pinned. The annex also carries the venue's
`capacityEnforcementCutover` line (the date production activated capacity
enforcement, resolved at the month's pinned parameter version, empty while no
such date is in force), and the daily flow CSV carries the same line as in
force on the day of the download.

**Legal basis line (revision 3.70).** Once the Rulebook amendment on the
Membership Pool is effective the monthly annex header carries a `legalBasis`
line naming the model and the K-TCD section header states it; until then the
annex asserts no counterparty-legal position.

**K-TCD exposure snapshot methodology (revision 3.70).** When the venue has
captured a month-end K-TCD row for you, the monthly annex (v3) carries it after
the rolling measures: the columns `asOf`, `rc`, `pfe`, `collateral`, `evGross`,
`evNet`, `ktcdGross`, `ktcdNet`, `rfWeighted`, `barrierMaxLossHeld` and
`nettingSets`, computed under the venue's pool methodology. One netting set per
(member, perpetual market) against the pool: RC is the replacement cost, your
negative unrealised PnL at the venue mark (a winning position is an obligation
of the pool, not an exposure); PFE is the supervisory factor of the venue
parameter store times notional (IFR Article 29 shape); EV = max(0, RC + PFE -
C); K-TCD = alpha x CVA x the sum over netting sets of EV x RF, with alpha 1.2
(Article 26) and CVA 1.5 (Article 32) applied uniformly to every netting set
and to both views; RF is decomposed pro rata over the open interest on the
opposite side of each market by the counterparty type of the members holding
it (a venue-defined methodology under the pool model, not an IFR-prescribed
one). View A recognises no collateral (C = 0, the venue's default on every
surface); view B recognises your positive balances after the haircuts held in
the venue parameter store (Article 30 shape), allocated pro rata by each set's
share of RC + PFE and capped at it. The classes the venue applies: the crypto
perpetuals (BTC, ETH, SOL, BNB, XRP, and since revision 3.75 HYPE, DOGE, ZEC,
NEAR and TAO) the residual class `other` (0.32; the
Article 29 table the venue seeds carries no crypto-asset row, so the highest
seeded factor applies); SP500 and NDX100, and since revision 3.75 US500 and
US100, `equity_index` (0.20); XAU, WTI and BRENT, and since revision 3.75 XAG,
`commodity` (0.18); EUR, GBP and JPY, and since revision 3.75 the USD/JPY
quanto USDJPY, `fx` (0.04). View B haircuts:
settlement-currency cash 0; EUR balances 1 (no recognition until the venue
operates an FX conversion of realised balances); every other balance,
crypto-asset balances included, 1 (no recognition: not Collateral under the
Rulebook, and the per-currency margin waterfall never reaches them on a
perpetual default). Barrier contracts are excluded from the netting sets (the
stop is guaranteed at the barrier and margin equals max loss) and appear as
the memo column `barrierMaxLossHeld`, the holder admission premiums held (the
premium each open contract margined at its open), never a notional; the class
row `barrier` = `other` is a methodology class that enters no figure. Every
class, haircut and factor is a row of the venue parameter store with an
effective date, a legal citation and a sign-off state, resolved at the
parameter version pinned on the capture, so the printed inputs are exactly the
ones the figures used. Inside the K-TCD block the annex prints a
`riskFactorMethod` line once the venue's compliance function has confirmed the
Article 26 risk-factor table for that snapshot's parameter version (it names
the rates and states that the risk factor is a venue-defined proxy over the
licence types of the members holding the opposite side, applied to all of a
member's open interest including positions held for clients; apply your own
determination); a `# ktcd factors` line naming alpha and CVA
(`# ktcd factors,alpha,1.2,cva,1.5`) once both rows the snapshot was computed
with are signed off; and, from the first month whose capture was taken on or
after the note's effective date, a methodology note printed as
`# ktcdMethodology` comment lines (revision v2.1) stating the computation in
words with every figure read from the venue parameter store at that pinned
version (the perpetual class and its factor, alpha, CVA, the settlement-cash
and crypto haircuts). An annex generated before a row was signed or the note
put in force keeps its exact bytes and its detached signature. Whether you may
recognise view B, and whether these classes and haircuts fit your own IFR
treatment, is your judgment: neither view is your own funds requirement or a
figure the venue presents as your own replacement cost, the venue expresses no
view on whether any collateral may be recognised under the IFR, and the figure
is venue-attributable input only, aggregated by you across venues.

**Classification notes (revision 3.27).** RFQ and barrier executed value now
classifies by trading capacity: the reg object an RFQ quote carries is
journaled on the venue's regulatory record (revision 3.27), and an acceptance
stamps both legs' records onto the trade, so the maker leg buckets by the
quote's capacity and the requester leg by the create's. Carry
`reg.tradingCapacity` on every quote: a quote sent without one classifies its
maker leg as UNKNOWN. History is never reclassified, so RFQ trades executed
before revision 3.27 keep counting as UNKNOWN capacity (the quote side was
never recorded for them), visible in the coverage percentage. The coverage
percentage uses floor rounding, so 100.00 appears only when every fill was
recognised, and a scope with no fills shows an empty value rather than a
vacuous 100.00. A member without a configured house reference cannot send DEAL
flow with an empty `clientUid` (the engine defaults an empty `clientUid` to your
house reference only for a market-maker member with one configured and answers
code 2010 otherwise); ask venue operations to configure a house reference
before enabling DEAL enforcement. If the venue parameter store
cannot resolve a measure, statements say PARAMETER_ERROR loudly rather than
print a fabricated figure.

**Production posture (revision 3.28).** In production the licensing registry
runs FAIL-CLOSED: a member without a configured profile is refused order
acceptance with code 2034 until venue operations configure one, because the
profile is a mandatory onboarding artifact sourced from the due diligence
file. On the production venue every member's licensing profile is set to the
ENFORCE stage before its first order, as a uniform admission condition rather
than a per-member agreement: capacity enforcement (code 2033) applies from
your first production order, no pre-enforcement history exists on production,
and your capacity coverage reads 100.00 from day one as long as every order
and quote carries a permitted capacity. Every DEAL-permitted member, market
makers included, also has its house reference configured before its first
order: without one, DEAL flow with an empty `clientUid` answers code 2010 at
the engine and DEAL flow with any other `clientUid` answers 2033
(classification notes above). The OFF and WARN stages are sandbox tooling for
proving your capacity tagging before production; the sandbox keeps per-member
staging. The sandbox stays fail-open, so integration testing is never blocked
by profile configuration. Where your home regime reports matched principal
differently, your RTS 22 field 29 report label is configured per member on the
venue side; it changes only what the venue's transaction reports carry, never
what you send on the wire and never the prudential bucketing, which keys on
the capacity itself (own-name execution accrues to dealing on own account, IFR
Article 4(1)(33)).

**Swap receivables memo (revision 3.70).** Where you hold yield contracts
as the taker (section 6h), the monthly annex reports your receivables (principal
plus the frozen reward, less what the writer has paid) as a memo by the writer's
counterparty type in the contract asset at six decimals, never as a netting set
and never naming the writer; whether a yield contract is a K-TCD transaction is a
question for your own prudential reading, and the memo is informational.

**What this is not.** The venue does not compute your own funds requirement,
does not know your flow on other venues, and the rolling figures become
meaningful only once the venue has aggregated enough history; the annex states
its coverage. In the sandbox, capacity enforcement begins only when venue
operations agree the stage with you and nothing changes for a member whose
profile has not been configured; on production the stage is ENFORCE from your
first order (Production posture above).

## 6g. Member web portal: RFQ functions, venue inquiries and onboarding

Orders and quotes are submitted through the APIs (REST, WebSocket and FIX).
The member web portal places book orders on SECURITY markets only, from its
DLT Market tab (Member Portal Guide section 10a; the ticket exists since
revision 3.66 and this paragraph names it since revision 3.70): LIMIT orders
as the same command `POST /v1/orders` sends, under the same Trader ID rule
(section 6, code 2021), capacity rule (section 6f), DEA controls, your ONE
order-entry bucket (code 1003, shared with every API credential, section 4)
and the order-to-trade regime of that market (code 1004), its cancel counting
as an order message exactly as `DELETE /v1/orders/{orderId}`; on every other
kind the portal places no book orders (perpetual, spot, barrier or swap
markets). It additionally submits requests for quotes,
quotes and acceptances under the RFQ rules, as the same commands the API
sends, subject to the same Trader ID rule and the same capacity rule. These
portal functions have no `/v1` surface. Two further functions of the member
WEB portal, inquiries and
onboarding, follow (they have no `/v1` API surface either; your systems
integrate over REST, WebSocket and FIX, these are for your operations staff in
a browser). A portal login is issued by venue operations and works at
ANY time; completing onboarding is never a precondition for the login, for API
keys or for the other portal functions. Since 19 August 2026 the portal sign-in
asks for a six-digit code from an authenticator app after the member code and
password, enrolled at your first sign-in (QR code and text key on the sign-in
page); the Member Portal User Guide (revision 1.3, section 1.1) has the details
and the reset procedure. API keys and the `/v1` API are unaffected.

**Inquiries.** Venue operations may open an inquiry with your firm (for
example a KYC refresh, a settlement question, a reconciliation request). It
appears under the portal's Inquiries tab as a message thread: the venue's
messages and your replies, with the inquiry's title and status. You can reply
while the inquiry is OPEN; a closed inquiry is read only. The portal shows an
indicator with your count of open inquiries. Replies are recorded by the venue
for audit.

**Onboarding.** Until venue operations mark your firm as onboarded, the
portal shows an Onboarding tab: a data form (legal name, LEI, registered
address, contacts, regulatory status), document upload (8 MB per file, at most
30 files and 64 MB per member; PDF, PNG, JPEG, modern Office documents and
plain text are accepted, legacy Office files, SVG, archives, macro-enabled
Office files and encrypted PDFs are refused; the venue stores a rebuilt copy
of every accepted file with metadata and active content removed, so a
document you download later can differ byte for byte from the file you
uploaded, Member Portal Guide section 12), save
as draft, and submit for review. You can resubmit while the venue has not yet
decided. Once venue operations complete your onboarding, the tab disappears
and the venue's record is authoritative; members onboarded manually outside
the portal never see the tab at all. Documents you uploaded remain part of
the venue's onboarding record.

## 6h. Yield contracts: writer-first fixed yield contracts on SWAP markets (private)

A **swap market** (`"kind": "SWAP"`, always `"mode": "RFQ"`) trades a bilateral
yield contract in ONE asset: a **writer** posts a standing **offer** ("I take
your BTC for 30 days and pay 4.50 percent annualised"), any other member
**takes** a size of it, the taker's principal in that asset moves to the writer
at the take, and principal plus a reward FROZEN at the take return to the taker
at maturity. Six markets are listed on the sandbox (revision 3.51):

| Market | Name | Asset | `qtyStep` | Minimum take (`minNotional`, in the asset) | Tenor (days) | Maximum rate | Per-offer / per-take cap (`maxOrderQty`) |
|---|---|---|---|---|---|---|---|
| USD-TBILL-SWAP | USD Fixed Yield Contract | USD | 0.01 | 100 | 7 to 364 | 2000 bps (20 percent) | 10,000,000 |
| EUR-TBILL-SWAP | EUR Fixed Yield Contract | EUR | 0.01 | 100 | 7 to 364 | 2000 bps (20 percent) | 10,000,000 |
| BTC-STAKE-SWAP | BTC Fixed Yield Contract | BTC | 0.0001 | 0.001 | 1 to 365 | 3000 bps (30 percent) | 100 |
| ETH-STAKE-SWAP | ETH Fixed Yield Contract | ETH | 0.001 | 0.01 | 1 to 365 | 3000 bps (30 percent) | 5,000 |
| SOL-STAKE-SWAP | SOL Fixed Yield Contract | SOL | 0.01 | 1 | 1 to 365 | 3000 bps (30 percent) | 100,000 |
| USD1-STAKE-SWAP | USD1 Fixed Yield Contract | USD1 | 0.01 | 100 | 1 to 365 | 3000 bps (30 percent) | 10,000,000 |

The names are how the venue and its members label the markets in
member-facing material; the wire carries the symbol only. The symbol tokens
TBILL, STAKE and SWAP are identifiers only: no contract references a bill rate,
a staking activity or a floating leg. The per-market bounds
(`swapMinTenorDays`, `swapMaxTenorDays`, `swapMaxRateBps`) and the fees are
published on `GET /v1/instruments` beside `baseAsset` (the asset), `qtyStep`,
`minNotional` and `maxOrderQty`; a `maxPositionQty` ten times the per-order cap
bounds your OPEN swap size on the market in both roles together. The crypto
caps are the round asset figures whose value at the listing marks sits under
the 10,000,000 USD cap of the USD, EUR and USD1 markets (100 BTC about
7,000,000 USD, 5,000 ETH about 9,400,000 USD, 100,000 SOL about 7,600,000
USD), the same value ceiling as the perpetuals' per-order notional cap; a swap
market has no mark, so the venue reviews the crypto figures against the market
from time to time and changes them under the Rulebook, and a change never
re-prices an existing offer or contract. `tickSize` is
`1` (every price field on a swap market, executions included, carries the rate
in whole basis points); `priceBandBps`, funding and risk tiers are absent or
zero: a swap market has NO price, NO mark and NO margin. Companion document:
`KronosX-Contract-Specifications-Swaps.md` (the product terms of the fixed
yield contracts, with the economics below).

**Admission (revision 3.70).** The six swap markets are ADMISSION markets
(`GET /v1/instruments` `accessMode` `ADMISSION`): you may post an offer or take
one only while your access to that market reads `ALLOWED` on
`GET /v1/account/parameters markets[].access`, else `3009` (the same code every
market answers a member whose access is denied; reduce-only flow is irrelevant
on a market without positions). The venue admits a member per market under the
objective criteria of the `SWAP_COUNTERPARTY` class published in the Rules
annex: the client category recorded for the member is eligible counterparty or
professional, the member has executed the Swap Product Annex to its Membership
Agreement, and the member has completed conformance testing on the test
environment's swap markets (Conformance Test Plan, Section W). The admission
covers both roles on that market. An open contract is never affected by a
change of admission (it runs to maturity and settles); a writer whose admission
lapses has its standing offers refused at the next take attempt (`3007` to the
taker) and withdrawn, and cannot post new ones. A refused admission may be
raised through the complaints procedure of Rules section 38. Apply through
venue operations. The class carries no numeric value: it is the published
criteria only, and the admission itself is the per-market Allowed venue
operations store for you.

**One sentence you must show your clients.** The writer's obligation to return
principal plus reward is UNSECURED: the venue never verifies what the writer
does with the asset, holds nothing against the return, and settles a short
writer partially (below). Only a Market Maker Member may act as Writer, and
only on its own account against proprietary capital: the Company designates
as Market Maker Member for the swap markets every Member that applies and
meets the Market Maker Member requirements; the Market Maker Agreement records
the Member's acknowledgement that its obligation to return Principal plus
Reward is unsecured and sets out the terms on which the designation ends. Any
Member may act as Taker. On the wire (revision 3.70): an offer by a member
without the designation, or an offer carrying a `clientUid` other than your
house uid, rejects `3013` once `GET /v1/instruments` publishes
`swapWriterEligibility` `MARKET_MAKER` on the market; while it reads `ANY` (the
sandbox until venue operations announce the activation) every member may
write, so the published value is always the enforced rule. A standing offer
of a writer whose designation is withdrawn is refused at the next take
(`3007`) and withdrawn. Your designation is on your onboarding record. Taking
is open to every member admitted to the market (section 7, `accessMode`).

**Economics (normative; the exact integer arithmetic the venue runs).**

- Sizes are quantities of the asset on the market's `qtyStep` (`qty` on every
  request), like spot. `notional = qty` in the asset. Every asset balance on
  the venue is kept to 6 decimals; that is the asset's minor unit here.
- `rateBps` is the annualised rate in whole basis points the writer pays
  (`450` = 4.50 percent), `1` to the market's `swapMaxRateBps`.
- `tenorDays` is the tenor in whole days (calendar days, weekends and holidays
  included), within the market's bounds. `maturityTs = openedTs + tenorDays x
  86,400,000 ms` exactly (the tenor runs from the take, not from midnight).
- `reward = floor(notional x rateBps x tenorDays / (10,000 x 365))` in the
  asset (ACT/365, floor to the 6th decimal), computed and FROZEN at the take.
  Examples: 1 BTC at 450 bps for 30 days pays `0.003698` BTC (`1 x 450 x 30 /
  3,650,000 = 0.0036986...`, floored); 10,000 USD at 420 bps for 91 days pays
  `104.712328` USD.
- **At the take** the taker's `qty` moves to the writer as principal (ledger
  kind `SWAPPRINCIPAL`, `refId` = the contract id, `ccy` = the asset), and
  BOTH parties pay the market's fee on the notional IN THE ASSET (`FEE`
  entries, `refId` = the take's `tradeId`; writer maker fee, taker taker fee,
  0.01 percent / 0.02 percent on every current listing, half-even rounding at
  the 6th decimal). Since revision 3.70 the market's `makerFeeRate` and
  `takerFeeRate` of `GET /v1/instruments` are what EVERY member pays on a swap
  market: fee tiers and programmes do not apply there, and the swap rows of
  `GET /v1/account/parameters` read source `INSTRUMENT` with a stored tier or
  programme value listed under `inert` (on the sandbox from the engine
  sequence venue operations announce, on production from the first day).
  Nothing further is charged at maturity, on a claim payment or on a final
  settlement. The taker must be able to pay `qty + takerFee` out of what it can pay in the asset right now:
  its free balance in the asset, and on a USD swap additionally no more than
  its `available` margin balance (a USD swap draws on your margin collateral,
  never below what your open perp positions need); else `3004`.
- **At maturity** (the first venue timer tick, at 1 s cadence, at or after
  `maturityTs`; a HALTED swap market still matures, a halt never becomes a
  default; a venue-wide halt defers the settlement to the first tick after
  resume) the writer pays `due = notional + reward` to the taker (ledger kind
  `SWAPSETTLE`, `refId` = the contract id): `paid = min(due, what the writer
  can pay in the asset)`. Paid in full, the contract closes `MATURED`. Short,
  the contract turns `DEFAULTED` with `outstanding = due - paid` as the
  taker's CLAIM: the writer's open offers on every swap market are withdrawn
  (`rfq` `canceled` frames), the writer may post no new offer while any claim
  against it is open (`3008`, and a take against a still-open offer of a
  blocked writer answers `3007` and withdraws the offer), and every later
  timer tick retries the claim from whatever the writer can then pay, in
  contract-id order; the last payment closes the contract `CURED`. Retries
  that can move nothing emit nothing.
- **Delisting** of a swap market withdraws every standing offer on it in the
  same action (an `rfq` `canceled` frame with the untaken `remainingQty` to
  each writer); the venue then closes every open contract at principal plus
  the reward accrued pro rata by elapsed WHOLE days (floor, capped at the
  tenor), status `FINAL_SETTLEMENT`, with the same shortfall handling; a claim
  survives the delisting until cured.
- **A writer in default management** (in liquidation or default on the venue)
  pays no outflow at all, a swap settlement included, exactly like a
  withdrawal: a maturity that falls inside that period settles nothing, the
  contract turns `DEFAULTED` with the whole due as the claim, and the venue's
  timer pays it in full on the first tick after the writer is out of default
  management (`CURED`). Nothing is lost, only deferred. Confirmed as the
  contract terms in the swaps contract specifications, Draft 0.2, section 9.5
  (revision 3.70): a `DEFAULTED` contract with `amount` 0 at maturity is a
  deferral, not a shortfall of funds.
- **Halted market:** no new offers or takes (`3003`), maturities settle.
  **Reduce-only or suspended member:** cannot post or take (`3002`); a writer
  that becomes suspended, reduce-only, enters liquidation or default has its
  offers refused at the next take (`3007`, offer withdrawn). **Liquidation of
  a party:** swap contracts RIDE THROUGH (the asset left the taker's balance
  at the take and a claim is a debt, not exposure); the venue's tear-up
  withdraws the liquidated member's open offers, nothing else. There is NO
  early close in this release: a contract lives to its maturity.
- **Caps:** an offer, and every single take, is at most `maxOrderQty`; your
  open swap quantity on a market in both roles together is at most
  `maxPositionQty` (`2011` for your own breach, `3007` when it is the writer's);
  at most **256 live contracts per member** across all swap markets and
  **4096 per market** (same codes); at most 64 open offers per writer (`2012`,
  the open-RFQ cap). Live means open or carrying a claim.

**Flow.**

| Endpoint | Description |
|---|---|
| `POST /v1/rfq` (swap market) | Post an OFFER: `symbol`, `qty` (the size on offer), `rateBps`, `tenorDays`, optional `minQty` (the smallest single take you accept; default and floor: the market's `minNotional`), optional `expiresInMs` (validity of the standing offer; default 86,400,000 = 24 h, minimum 60 s, maximum 30 days), `clOrdId`, `clientUid`, optional `reg`. NO `side`, `barrier` or `closeContractId` (`2013`; and `rateBps` / `tenorDays` / `minQty` sent on a non-swap market are `2013` too). `qty` and `minQty` are asset quantities on the market's `qtyStep` grid (`2002` off it). Answer: the RFQ view with `kind` `SWAP`, `rfqId`, `qty`, `remainingQty` (= `qty`), `expiryTs`, `createdTs`, `rateBps`, `tenorDays`, `minQty`, `asset` and no `quotes` member. Terms outside the market (`rateBps`, `tenorDays`, `qty` below `minNotional` or above `maxOrderQty`, `minQty` above `qty`, validity, or a size whose principal plus reward would leave the venue's exact-money range) reject `2036`; a writer with an outstanding claim rejects `3008`; a member that is not a Market Maker Member, or an offer carrying a `clientUid` other than your house uid, rejects `3013` once `swapWriterEligibility` reads `MARKET_MAKER` (revision 3.70) |
| `GET /v1/rfq/open?symbol=` | The OFFERS BOARD: every live offer you may take, rows `rfqId`, `symbol`, `kind` `SWAP`, `side` `BUY` (the writer receives the asset), `asset`, `rateBps`, `tenorDays`, `qty`, `remainingQty`, `minQty`, `expiryTs`, `createdTs`; writers anonymous, your OWN offers excluded (they sit on `mine`), Market Segments applied (an offer from a member outside your acceptance set is not on your board), sorted `rateBps` descending then `rfqId` (best for the taker first). Polled REST, never pushed |
| `GET /v1/rfq/mine` | Your own open offers with their live `remainingQty` (no quotes: swap offers are never quoted) |
| `POST /v1/rfq/{rfqId}/accept` (swap offer) | TAKE `qty` of the offer: `qty`, `clOrdId`, `clientUid`, optional `reg`; NO `quoteId` (`2037` if sent). Executes at once. Answer: `rfqId`, `quoteId` 0, `tradeId`, `symbol`, `side` `SELL` (you deliver the asset), `price` (the rate in basis points as a string), `qty`, `contractId`, `notional`, `rateBps`, `tenorDays`, `reward`, `maturityTs`, `asset`, `fee` (your taker fee, in the asset), `remainingQty` (the offer's remainder after your take), `ts`. `qty` must be on the `qtyStep` grid and above zero (`2002`), at most `remainingQty` and at most your `maxOrderQty`, and at least `max(minQty, minNotional)` UNLESS it is the whole remainder (a tail smaller than the minimum can always be cleared); otherwise `2038`. Your own offer: `2026`. An unknown, withdrawn, expired or ineligible offer: `2024`, and for an ineligible offer (a writer outside your Market Segments) that answer comes BEFORE every other check, whatever the body says, so the route never confirms that such an offer exists. The writer no longer fit (suspended, reduce-only, in liquidation or default, blocked by a claim, or whose Market Maker designation ended, revision 3.70): `3007` and the offer is withdrawn; the writer at its open-swap or contract-count caps for your size: `3007` and the offer STAYS (try smaller or later) |
| `POST /v1/rfq/{rfqId}/cancel` | Writer withdraws the offer (untaken remainder). Never rate-limited |
| `POST /v1/rfq/{rfqId}/quotes` on a swap market | Refused, `2037`: swap markets have offers and takes, never quotes |
| `GET /v1/swaps?status=&limit=` | Your swap contracts, both roles. Default (`status` omitted or `OPEN`): the live ones, open or carrying a claim; `status=CLOSED`: your recently settled contracts (newest first, `limit` default 100, maximum 1000, kept since the last venue start; the durable record is your ledger, the `SWAPSETTLE` entries, and the `swaps` event stream, section 11); `ALL`: both, live rows first. Any other value answers `2013`. Rows: `contractId`, `symbol`, `asset`, `role` (`WRITER` \| `TAKER`), `qty`, `notional`, `rateBps`, `tenorDays`, `reward`, `openedTs`, `maturityTs`, `status` (`OPEN` \| `DEFAULTED` \| `MATURED` \| `CURED` \| `FINAL_SETTLEMENT`), `paid` (cumulative asset paid by the writer so far), `outstanding` (the claim still owed, 0 unless `DEFAULTED`), `clientUid` (your side's), `rfqId`, `tradeId`, and on closed rows `closedTs`. The counterparty is never named |

Rate limits, Trader ID (`reg.traderId`, `2021`) and trading capacity
(`reg.tradingCapacity`, `2033`) apply to offers and takes exactly as to any
RFQ create and accept. Partial takes: an offer's `remainingQty` decrements
take by take; at zero the offer closes (its `accepted` frame carries
`remainingQty` 0, no extra frame); an offer partially taken and then reaching
its validity end emits `expired`.

**Trade effects.** Every take prints in `GET /v1/executions` and on the WS
`execution` channel, and anonymously on the public trades feed (`GET /v1/trades`,
WS `trades.{symbol}`), flagged `SWAP` (a new value in the `flags` vocabulary,
ranking above `RFQ`), `price` = `rateBps` as a whole number, `qty` = the taken
size, `side` `SELL` for the taker (it delivers the asset), fees in the asset,
writer maker, taker taker. It is not price data: it updates no last-trade
print, fires no conditional order and feeds no circuit breaker (a swap market
has no price). Swap contracts never appear in `GET /v1/positions`, open
interest or margin; the asset legs appear on `GET /v1/account` (the `assets`
array for BTC / ETH / SOL / EUR / USD1, the USD row for the USD swap) and on
`GET /v1/account/ledger` / the WS `wallet` channel as `SWAPPRINCIPAL`,
`SWAPSETTLE` and `FEE` entries with `ccy` = the asset. Swap markets are
within MiFIR transaction reporting by the venue's decision of 10 September
2026 (revision 3.70): every take is an RTS 22 transaction (the rate reported
in basis points, the size in the asset) and the offer record is kept under RTS
24; `clientUid` is required on every offer and take and the section 6d
identity duties apply. The venue's reporting record admits the swap markets
from the date venue operations announce; until that date the section 6d
missing-identity report lists `clientUid`s that traded on perpetual and
barrier markets only.

**WebSocket.** The private `rfq` channel carries the offer lifecycle for the
WRITER (`created`, `canceled`, `expired`, each with `rateBps`, `tenorDays`,
`minQty`, `remainingQty`) and `accepted` to BOTH parties of a take (`role`
`WRITER` \| `TAKER`, the taken `qty`, `remainingQty` after it, `contractId`,
`tradeId`). The new private channel `swaps` (section 10.3, guaranteed
delivery, event-class and backfillable by cursor like `wallet`) carries the
contract lifecycle to both parties, every frame with `contractId`, `symbol`,
`asset`, your `role`, `qty`, `notional`, `rateBps`, `tenorDays`, `reward`,
`maturityTs`, `status`, `rfqId`, `tradeId`, your `clientUid`, `ts` and a
ROLE-SIGNED `amount` (what your role paid by this event is negative, what it
received positive): `opened` (`amount` = the principal, negative for the taker),
`matured` (`amount` = the due, `paid` = the cumulative amount paid, `outstanding`
0, `status` `MATURED`), `default` (`amount` = what could be paid this time,
`paid` cumulative, `outstanding` above 0, `status` `DEFAULTED`; also every later
partial retry), `cured` (the last retry, `outstanding` 0, `status` `CURED`) and
`finalSettlement`; settlement frames also carry `reason` (the same word as the
resulting `status`).

**Transports.** Offers and takes are REST and WebSocket only (no FIX order
entry or RFQ). Like every other RFQ execution, a take's print still reaches
your FIX sessions as a drop-copy ExecutionReport (`OrderID(37)` 0, `LastPx(31)`
= the rate in basis points, `LastQty(32)` = the taken size) and the FIX trade
tape (35=X, `9014=SWAP`); see the FIX guide, tag `9014`.

## 6i. DLT Market: tokenised securities on the Kronos X chain (private)

Since revision 3.66 (2026-09-10). PM MTF Ltd operates a DLT trading and
settlement system under Regulation (EU) 2022/858 (the DLT Pilot Regime). Its
instruments are DLT financial instruments: shares, bonds, money-market
instruments and fund units issued, recorded, transferred and stored on the
Kronos X chain, the venue's permissioned ledger. This section is the wire
contract; the Rulebook DLT annex and the Settlement and Clearing Policy are the
legal terms it implements.

### The market

- A DLT financial instrument is a market with `kind: SECURITY` on
  `GET /v1/instruments`. Its `baseAsset` is the instrument's ISIN and equals its
  `isin`; `unitScale` may exceed 1 (fractional shares: one contract is
  1/unitScale of a share or of a nominal unit). Everything in section 6a applies
  as written for SPOT: fully prefunded, no positions, no margin, no funding, no
  `reduceOnly`, the derived reservations, the fill-time funded check
  (`cancelReason` `SPOT_UNFUNDED`), the `assets[]` view of your holdings. A
  SECURITY market trades on the central limit book or by RFQ per its `mode`.
- Your trading balance in the ISIN appears in `assets[]` like a spot base asset.
  It is the mirror of what the chain holds IN TRADING for you: the sum of the
  custody wallets of your clients (one wallet per `clientUid` you have traded or
  registered, plus your house wallet for an empty `clientUid`) and of the
  external wallets you sponsor. The venue reconciles the two continuously; a
  difference is a venue incident, never something you act on.

### Settlement and finality

- Every fill on a SECURITY market creates a settlement obligation. The venue's
  block producer folds the fill into a `DvpSettle` transaction (seller's client
  wallet to buyer's client wallet, the cash leg recorded on the venue's books as
  today), validators re-execute the block and sign it, and the block is FINAL
  when at least two thirds of the validator set including one validator not
  operated by PM MTF Ltd have signed, typically within two seconds of the fill.
  Since revision 3.70 the chain rules themselves keep that promise across
  changes of the validator set: a change never removes the last validator not
  operated by PM MTF Ltd from a set that has one, never leaves the operator's
  validators at or above the quorum of such a set (a replacement joins before
  the seat it replaces leaves), and a validator's key or status changes only
  with that validator's own signature beside the venue registrar's.
  The three moments of the Settlement and Clearing Policy: entry and
  irrevocability at the acknowledged fill (the trade cannot be recalled),
  finality at the attested block. An obligation survives a venue restart
  (revision 3.70): at every start the venue re-derives from its own
  transaction record any fill its ledger intake had no durable record of (a
  stop or a crash in the moment after the fill was acknowledged), so such a
  fill settles in the first blocks after the restart, exactly once.
- `GET /v1/chain/settlements` lists your obligations (`state` `Pending`,
  `Settled` or `Failed`, the block height and the transaction hash). A `Failed`
  obligation is a settlement failure under the Rulebook's failure management
  procedure; venue operations contact you. The units a fill delivers come from
  the wallets whose holdings in trading make up your balance, in this order:
  the selling client's custody wallet, the external wallets you sponsor for
  that client (their `clientRef` names the client's custody wallet), then your
  own holdings in trading as the omnibus fallback: your house wallet and, since
  revision 3.70, the external wallets you sponsor for yourself (no `clientRef`).
  A client's shortfall covered from your own holdings is reported on the
  transaction (`houseCoveredUnits`, the house wallet and your own external
  wallets together); an external wallet's free units are never touched.
- While the validator quorum is not reached (an "unattested tail") trading
  continues and fills stay irrevocable; only movements OUT of trading wallets
  wait, answered `3012` LEDGER_FINALITY_PENDING.

### Routes (private, TRADE credential unless noted)

The four writes below are gated exactly like order entry: a READ_ONLY (drop
copy) credential answers `1005` KEY_NOT_PERMITTED before anything happens, and
each call charges your ORDER rate bucket (`1003` with the refill wait when it is
exhausted, section 4), since every call is one engine command plus one chain
instruction (revision 3.69).

| Route | Purpose |
|---|---|
| `GET /v1/chain/wallets` | Your wallets (custody per client and sponsored external) with holdings per ISIN (`free`, `locked` = in trading). READ_ONLY may call it. |
| `POST /v1/chain/wallets/custody` `{ clientUid, professional }` | Register a client's custody wallet ahead of its first trade (the venue also creates it at the first fill). Since revision 3.70 the answer carries `walletId`: a custody wallet id is the venue's keyed derivation for that client, read here and on `GET /v1/chain/wallets`, never computed by you. |
| `POST /v1/chain/transfers` `{ clientUid, isin, to, units }` | Move units OUT of one of your custody wallets to any wallet (another member's custody wallet, an external wallet). Since revision 3.70 the chain's own rules are checked first on its committed state (a frozen party, the instrument's transfer rules, a lock-up, the client wallet's holding): such a refusal answers `5003` with `chain.reason` before your engine balance is touched. Then your engine balance is debited (a debit above your free balance in the ISIN is refused with `3001` INSUFFICIENT_MARGIN, the engine's own code, in the standard envelope, nothing moves; revisions 3.66 to 3.69 named `3004` here, which the venue never answered) and the chain moves the units; a refusal at the block re-credits you first and answers `5003` with `chain.reason` after the re-credit was attempted. Since revision 3.70 EVERY refusal of the operation is `5003` with `chain` (`state` `Rejected`, `reason`): an unknown `isin`, a `clientUid` you hold no custody wallet for, an unknown destination wallet, a non-positive `units`, each refused before any check of the chain and before any debit, with an all-zero `chain.txHash`. The source wallet is always derived from YOUR credential and the `clientUid`, never from a field. |
| `POST /v1/chain/unlock` `{ wallet, isin, units }` | Return units of an external wallet you sponsor from trading to the wallet's free balance; the chain's rules checked first, then the engine debited (`3001` above your free balance); a wallet you do not sponsor, an unknown `isin` or a non-positive `units` answers `5003` with `chain.reason` (revision 3.70). |
| `POST /v1/chain/instructions` (signed envelope) | Submit an instruction signed by an external wallet's own P-256 key: `WalletRegister` (class `External`, `memberId` = you as sponsor: since revision 3.70 it must be your own member id, `5003` otherwise, and the venue registrar co-signs the registration on your submission, which is your attestation of the `professional` flag; the optional `clientRef` names the custody wallet of your client the wallet trades for), `Lock` (free to trading; credits your engine balance at finality), `Transfer` between wallets. The envelope carries the transaction fields, `signerKey` and `signature` (DER ECDSA P-256 over SHA-256 of `"KXC:instr:"` and the canonical body; the encoding is specified below, so the route is implementable in any language from this document). `200` with the status; poll `GET /chain/v1/instructions/{hash}`. An envelope already final on the chain answers `5003` with `error` `DuplicateInstruction` and its `Final` status and is never applied twice; a body the chain refused at an earlier block may be posted again; `tsNanos` must lie within the venue's validity window (older than the window, or ahead of the venue clock by more than a small allowance for your wallet's clock, is refused; since revision 3.70 the stamp is anti-replay data only and never sets the time of the block that records the instruction, which is the venue's own). The optional `registrarSignatures` list carries at most one entry per registrar key of the venue and at most 255 entries, each `keyIndex` 0 to 255; a longer list or an index outside that range is refused before any signature is checked (revision 3.70). An instruction whose engine credit would not fit the venue's ledger arithmetic is refused with the reason `size overflow` at the block that would have recorded it, never applied in part. |
| `GET /v1/chain/settlements?tradeId=&state=&limit=` | Your settlement obligations. |

Since revision 3.70 every body and answer of this table is typed in `/openapi.json`
(2.35.0) AS SERVED, for generated clients and the interactive console: the request
schemas `ChainCustodyWalletRequest`, `ChainTransferRequest`, `ChainUnlockRequest` and
`ChainInstructionRequest` (the member subset of the signed envelope, with named
examples; the envelope example is a shape the SDK must sign, never a body that
executes as pasted), and the answers `ChainInstructionResult` (`{ chain }`, the
status object `ChainInstructionStatus`), `ChainCustodyWalletResult` (`{ chain, walletId }`),
`ChainWalletsResult` (`{ height, wallets }`),
`ChainSettlement` (a bare array) and `ChainHead`. None of them is the standard
envelope of section 5, exactly as the routes have always answered; documentation
only, nothing on the wire moved.

### Signing an instruction: the canonical encoding (revision 3.69, schema 2 since 3.70)

The request body of `POST /v1/chain/instructions` is JSON, but what you SIGN is
not the JSON: it is the canonical body of the transaction the JSON describes,
a fixed-width binary layout, so every encoder hashes the same bytes for the
same values. This note is the complete encoding as the venue's own encoder
writes it; an SDK package is a follow-up and is not required. Since revision
3.70 the layout is schema 2: no client identifier is in any body (the two
client uid strings of the settlement kind and the `clientUid` of a registration
left it, `house` and `clientRef` took the registration's place), so nothing a
member types about a client ever enters the ledger's bytes.

**Primitive encodings.**

- Integers are little-endian and fixed width: `u8` one byte, `u16` two bytes,
  `i32` four bytes, `i64` eight bytes. A `bool` is one byte, `1` or `0`.
- A `string` is a `u32` byte length followed by the UTF-8 bytes; an absent and
  an empty string encode identically (`u32` 0). A `bytes` value is a `u32`
  length followed by the raw bytes.
- A `hash` (wallet id, token id, reference) is exactly 32 bytes, in the order
  of the 64 hex digits the API shows (no length prefix); an absent hash is 32
  zero bytes.
- Enumerations are one byte carrying the numeric value: `kind` WalletRegister
  1, TokenCreate 2, Issue 3, Transfer 4, Lock 5, Unlock 6, DvpSettle 7, Freeze
  8, Unfreeze 9, Redeem 10, CorporateAction 11, ContractUpdate 12,
  ValidatorSetChange 13, Migrate 14; `auth` WalletKey 1, VenueCustody 2, Issuer
  3, Registrar 4 (a member's instruction is always `WalletKey`); `walletClass`
  Custody 1, External 2, Issuer 3, Treasury 4; `actionKind` Coupon 1, Dividend
  2, Split 3.

**The body.** Every field is written in this order on every kind, unused ones
as zero, empty or the 32 zero bytes. The names are the JSON fields of the
envelope; `tsNanos`, `from`, `to`, `token`, `units`, `walletClass`,
`publicKey`, `memberId`, `clientRef`, `professional`, `signerKey` are the ones
a member's three kinds use, the rest are operator and venue fields that a
member leaves empty.

| # | Field | Type |
|---|---|---|
| 1 | schema version | `u16`, currently 2 |
| 2 | `kind` | `u8` |
| 3 | `auth` | `u8`, 1 |
| 4 | `tsNanos` | `i64`: your signing time in Unix nanoseconds; the venue refuses an instruction whose `tsNanos` is more than 300 seconds from its clock, and a body hash it has already recorded in a block or queued for one |
| 5 | engine sequence number | `i64`, 0 on a member instruction |
| 6 | `from` | `hash` |
| 7 | `to` | `hash` |
| 8 | `token` | `hash` |
| 9 | `units` | `i64` |
| 10 | trade id | `i64`, 0 |
| 11 | buyer member id | `i32`, 0 |
| 12 | seller member id | `i32`, 0 |
| 13 | price ticks | `i64`, 0 |
| 14 | cash ccy | `string`, empty |
| 15 | cash micros | `i64`, 0 |
| 16 | cash leg ref | `hash`, zero |
| 17 | cash token | `hash`, zero |
| 18 | house covered units | `i64`, 0 |
| 19 | `walletClass` | `u8` (2 on your WalletRegister, 0 otherwise) |
| 20 | `publicKey` | `bytes`: on WalletRegister the wallet's 65-byte SEC1 uncompressed P-256 point (`0x04`, X, Y); empty otherwise |
| 21 | `memberId` | `i32`: on WalletRegister your member id, the sponsor; 0 otherwise |
| 22 | `house` | `bool`, 0 on a member instruction (the venue's custody registrations use it) |
| 23 | `clientRef` | `hash`: on WalletRegister, optionally, the custody wallet id of your client the wallet trades for (from `POST /v1/chain/wallets/custody` or `GET /v1/chain/wallets`); the 32 zero bytes otherwise |
| 24 | `professional` | `bool` |
| 25 | `isin` | `string`, upper case, empty on a member instruction |
| 26 | `symbol` | `string`, empty |
| 27 | `name` | `string`, empty |
| 28 | `authorised` | `i64`, 0 |
| 29 | `unitScale` | `i32`, 0 |
| 30 | `contract` present | `bool`; when 1 it is followed by `allowedHolderClasses` `u8`, `transfersAllowed` `bool`, `lockupUntilTsNanos` `i64`, `professionalOnly` `bool`; a member writes 0 |
| 31 | `target` | `string`, empty |
| 32 | `registerHash` | `hash`, zero |
| 33 | `actionKind` | `u8`, 0 |
| 34 | `rate` | `i64`, 0 |
| 35 | `ground` | `string`, empty |
| 36 | `caseReference` | `string`, empty |
| 37 | validator add present | `bool`, 0 (when 1: id `i32`, name `string`, operator `string`, external `bool`, public key `bytes`) |
| 38 | validator remove | `i32`, 0 |
| 39 | `signerKey` | `bytes`: your wallet's 65-byte public key, the key that signs |

**The digest and the signature.** `signature` is an ECDSA P-256 signature in
DER form (RFC 3279 `SEQUENCE { r INTEGER, s INTEGER }`) over the 32-byte
digest `SHA-256("KXC:instr:" || body)`, where `"KXC:instr:"` is the ten ASCII
bytes of that tag and `body` is the layout above; sign the digest as given (a
"sign hash" primitive, not a "sign message" one that would hash a second time).
The venue verifies with the key in `signerKey`, which must be the key of the
wallet in `from` (for a WalletRegister, the key in `publicKey`). Signature
values are not deterministic and never enter a hash: the transaction hash
that `200` returns, that `GET /chain/v1/instructions/{hash}` takes and that the
public record shows is `SHA-256("KXC:tx:" || body)` over the same body, as 64
lower-case hex digits.

**Identifiers.** An external wallet's id is
`SHA-256("KXC:wallet:" || publicKey)` over the 65 key bytes; it is the `from`
of your WalletRegister and of every later instruction from that wallet
(`GET /v1/chain/wallets` lists it once registered). A custody wallet's id
(your clients' wallets, your house wallet) is the venue's KEYED derivation
under a key only the venue's block producer holds: you read it from
`POST /v1/chain/wallets/custody` and `GET /v1/chain/wallets`, nobody can
compute it from a member number and a client identifier, and no client
identifier is in any block (revision 3.70). A token id is the
`tokenId` of `GET /chain/v1/tokens/{isin}`. In the JSON envelope hashes and
keys travel as hex (64 digits for a hash, 130 for a key, a `0x` prefix
optional), `tsNanos` and `units` as numbers; the other fields may be omitted.

**The three member kinds.** `WalletRegister`: `from` = your wallet id,
`walletClass` `"External"`, `publicKey`, `memberId` = you, optionally
`clientRef` (the custody wallet id of your client the wallet trades for, so a
fill of that client can deliver from the wallet's units in trading) and
`professional`; you submit it, the venue registrar co-signs it, and the wallet
is registered under you. `Lock` (free to trading, credits your engine
balance in the ISIN at finality): `from` = `to` = your wallet id, `token`,
`units`. `Transfer` (wallet to wallet, outside trading): `from` = your wallet
id, `to` = the destination wallet id, `token`, `units`; the token's transfer
rules apply (`5003` with `chain.reason` when refused). An engine-first movement
(a custody transfer out, an unlock, a redemption of a custody holding) whose
debit the venue could not confirm, a leader change while the instruction was in
flight, answers `5000` with a sentence naming the ledger note `kxc:<txHash>`:
nothing was queued on the chain, compare `GET /v1/account/ledger` for that
note before resubmitting (revision 3.70).

### Freezes (revision 3.70)

The venue registrar may freeze a custody wallet or a DLT financial instrument
(a court or authority order, the step before a migration, a documented key
recovery). A frozen wallet holds its units where they are: no transfer, no
lock, no unlock, no settlement into or out of it (a fill that reaches the chain
against it is a recorded settlement failure), and from the block's finality
every order, amend, RFQ request, quote and accept of that client on a SECURITY
market answers `2049` TOKEN_RESTRICTED before the engine (REST, WebSocket, FIX
and the member portal alike; the client's resting orders on SECURITY markets
are cancelled with `cancelReason` `ADMIN_CANCEL` when the freeze is taken, and
so are its open RFQ requests and live quotes there); your other clients
and markets are not touched. A frozen instrument halts its SECURITY markets
(`3003` on new orders, no uncross while halted, resumed by venue operations
after the unfreeze, never by the unfreeze alone) and answers `2049` on the
same surfaces. The public record shows `status` `Frozen` on the wallet and, on
the instrument, `status` `Frozen` with `frozenBy` `REGISTRAR` or `ISSUER` (an
issuer may freeze and unfreeze its own instrument; it can neither lift nor
replace the registrar's freeze), and nothing else about the measure: the
ground and the case reference are never on the public record.

### The public record (no authentication)

The explorer at `https://explorer.perpetuals.com` and its JSON API
`/chain/v1/*` on the venue (`head`, `blocks`, `blocks/{height}`, `tx/{hash}`
with a Merkle inclusion proof, `tokens`, `tokens/{isin}` with the integrity of
issue, `wallets/{id}`, `wallets/{id}/proof?isin=`, `validators`, `integrity`,
`settlements`, `instructions/{hash}` and, since revision 3.68, `valuation`) show
every block, transaction and holding. `valuation` is served from the venue's
last evaluation (the 60 s sweep's, or a refresh at most one second old); a
transaction or wallet URL of the explorer whose value is not 64 hexadecimal
digits shows the explorer's own not-found page, never its unavailable page
(revision 3.70). Since revision 3.70 `head` and
`validators` carry `externalValidators`, the number of validators in the set in
force not operated by PM MTF Ltd, beside `requireExternal`, the configured rule
(the explorer states the finality rule from both: a set without an external
validator is said so, never dressed up); `head` also carries `headValidators`
and `headQuorum`, the size and the quorum of the set that signed the head
block, which `headSignatures` counts against, while `validators` and `quorum`
describe the set for the next block. A route that takes a hash (`tx/{hash}`,
`wallets/{id}`, `wallets/{id}/proof`, `instructions/{hash}`) answers `400`
`hash must be 64 hex digits` (or `wallet id must be 64 hex digits`) on
anything but 64 hex digits, a `0x` prefix allowed, and `404` on a well-formed
hash it does not hold. Wallets are keyed hashes; the mapping to a member or a client exists
only in the venue's books and on your own `GET /v1/chain/wallets`, and no block
byte carries a client identifier. Settlement rows on the public record carry no
member identity; a registrar action carries no ground and no case reference
there.

### The Article 3 figures (public)

Since revision 3.68 (2026-09-10). Regulation (EU) 2022/858 limits what a DLT
market infrastructure may record: a share only while its issuer's market
capitalisation is below EUR 500 million, a bond or money-market instrument
while its issue size is below EUR 1 billion, a fund unit while its assets under
management are below EUR 500 million, and no new instrument once the aggregate
market value of every recorded instrument reaches EUR 6 billion; at EUR 9
billion the operator activates its transition strategy. `GET /chain/v1/valuation`
(no authentication, the explorer's Valuation page) publishes the live figures:
`status` (`OK`, `WATCH`, `ADMISSION_CAP`, `TRANSITION`), `aggregateEur`,
`thresholds`, `fx` (the ECB USD per EUR reference rate in use) and per
instrument `circulatingUnits`, `price`, `priceSource` (`AUCTION`, the closing or
opening auction price of section 6j, else `LAST_TRADE`, else `MARK`),
`marketValueEur`, the declared `basisEur` and `instrumentCapEur`. An instrument
without a price counts at its declared basis. The operator records one
valuation per instrument and calendar day and reports the month to its
competent authority; nothing in this section changes your trading.

Since revision 3.70 the record also publishes `monthlyAverage`, the Article
3(4) monthly average aggregate market value of the LAST COMPLETE calendar month
(`month`, `days` = the recorded days it averages, `aggregateEur`, `status` on
the same ladder, `perIsin[]` with `isin`, `class`, `averageEur`, `days`), and
`monthlyAverageRule`, the sentence saying how it is used. The arithmetic is the
Regulation's own and there is exactly one: per instrument the mean of its daily
EUR closing prices over the days of the month it was priced times the units
recorded on its last day (an instrument unpriced all month at its declared
basis), `perIsin[].averageEur` being that contribution and `aggregateEur` their
sum, the same figure the admission refusal projects from. The Regulation
applies that average to admissions in the following month and to the
transition decision: the operator refuses a new instrument when EITHER the
live aggregate at the moment of admission or the monthly average would reach
the EUR 6 billion cap, and decides the transition strategy on the monthly
average, a single day's live figure above EUR 9 billion being a warning and
not an activation. The figure is frozen once its month closes. While fewer
than one complete month is recorded, `monthlyAverage` is `null` and the rule
sentence says so. The same governing figure is published flat as
`monthlyAverageEur` and `monthlyAverageMonth` (`null` before the first complete
month), `valuationRule` states the rule, and `dailyAggregateReachesTransition`
flags a one-day spike of the day's aggregate above the transition figure, which
is reported and never activates the transition strategy: `TRANSITION` follows
the monthly average alone, `ADMISSION_CAP` the monthly average or the day's
aggregate. Additive; every field of revision 3.68 is unchanged.

### Codes

`3012` LEDGER_FINALITY_PENDING (a movement out of trading refused while the
tail is unattested; retry when `/chain/v1/head` shows `unattestedTail: false`),
`5006` LEDGER_UNAVAILABLE (answered only when the chain host runs without its
engine bridge, a venue-side configuration fault and never a member error: retry
after a minute, then contact venue operations; 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), `5003`
with `chain.reason` for every refusal of your instruction, the venue's checks
before the debit (unknown ISIN, no custody wallet for that client, unknown
destination wallet, a wallet you do not sponsor, non-positive units) and the
chain's own (frozen wallet or instrument, the instrument's transfer rules, an
overdraft) alike, `chain.state` `Rejected` in both cases (revision 3.70; a
pre-flight refusal carries an all-zero `chain.txHash`; `DuplicateInstruction` for
an envelope already final), and `2049` TOKEN_RESTRICTED on order entry, amends,
RFQ requests and quotes for a frozen instrument or a frozen client custody
wallet (revision 3.70, the freezes paragraph above). An engine refusal of
the debit keeps the engine's own code, `3001` INSUFFICIENT_MARGIN (the amount
above your free balance in the ISIN, your reservations kept), in the standard
`{ code, msg, ts }` envelope with no `chain` object; revisions 3.66 to 3.69
named `3004` here, which the venue never answered on these routes.

## 6j. Call auctions on SECURITY markets (private)

Since revision 3.67 (2026-09-10). A SECURITY market may carry an auction schedule:
one opening call and one closing call per calendar day at local times in the
market's zone. `GET /v1/instruments` publishes it as `auction` (`schedule`
`LOCAL_DAILY`, `zone`, `openCallStart`, `openUncross`, `closeCallStart`,
`closeUncross`, local wall times) beside the live `tradingPhase` and, after the
first uncross that traded, `lastAuctionPrice` and `lastAuctionTs`. A SECURITY
market without the block trades continuously exactly as before. A SECURITY
market with the block publishes `tradingHours` `SCHEDULED` (a market without
one reads `CONTINUOUS`): its daily hours are the `auction` block of its
instruments row, the phase in force is `tradingPhase` on that row and on the
public `status.{symbol}` phase frame, and neither `GET /v1/status` nor the
`venueEvents` channel announces the overnight `CLOSED` phase; the Trading hours
statements of sections 5, 6 and 10.7 and of the contract specifications apply
to every other market.

### Phases and what you may send

| `tradingPhase` | Meaning | Order entry |
|---|---|---|
| `CLOSED` | Before the opening call and after the closing uncross | GTC and GTD LIMIT orders rest for the next call; cancel and amend work; IOC, FOK, MARKET and conditional orders reject `2046` |
| `CALL_OPEN` | The opening call: orders accumulate, nothing matches | as CLOSED |
| `CONTINUOUS` | After the opening uncross until the closing call | every order type as today |
| `CALL_CLOSE` | The closing call | as CLOSED |

The phase changes arrive on the public `status.{symbol}` channel as
`{ tradingPhase, nextPhaseTs }`; each uncross arrives there as
`{ auction: { price, qty, surplusQty, surplusSide } }` (`price` absent, since
the venue omits null fields, and `qty` `0` when nothing could trade). During a
call your working orders may sit on a crossed book; that is the state the
uncross resolves.

A halted market never prints (revision 3.69): while the venue is halted or the
market's status is anything but `TRADING` (`HALTED`, and since revision 3.70
stated here in full, a `REDUCE_ONLY` or `POST_ONLY` session too) a due uncross is
deferred, `tradingPhase` stays in the call and the uncross runs once the market
is trading again. When venue operations change or remove a market's schedule
while it is in a call or closed (revision 3.70), the market can move straight
into `CONTINUOUS`: the phase frame arrives (`nextPhaseTs` `0` when the schedule
was removed, after which `auction` is absent from `GET /v1/instruments`), and
any orders still crossing are matched through ordinary continuous trading in
their time priority (the earlier order rests, the later one takes at the resting
price, ordinary fees, no `AUCTION` flag) so that continuous trading never
starts with a crossed book; there is no uncross frame and `lastAuctionPrice`
does not move. Such a change is not made while the market is halted. The same
replay resolves whatever a call left crossing without an uncross print (every
candidate outside the collar, a pair of your own orders, an ineligible pair);
two of its outcomes have their own `cancelReason` (revision 3.70): a `POST_ONLY`
order that rested through the call and would cross on re-entry is canceled
`POST_ONLY_WOULD_CROSS` (an acknowledged order is never rejected), and if a
re-entry print trips the circuit breaker the market halts at once and a
remaining order that would cross is canceled `INSTRUMENT_HALT` while one that
would not rests as usual.

### The uncross

At the scheduled time the venue computes ONE price and executes every crossing
order at it: the price with the largest executable volume, ties broken by the
smallest surplus, then by closeness to the reference (the last trade, which an
auction print also updates, else the mark), then the lower price; a candidate outside
the market's `priceBandBps` around the reference is not eligible. Orders are
paired in price and time priority; every print carries `flags: "AUCTION"` and
both sides pay the MAKER fee rate; an order the fill-time funded check finds
underfunded is cancelled `SPOT_UNFUNDED` like on any spot fill. If no price
trades, the call ends with an empty uncross and the phase advances; resting
orders stay for the next one. Auction prints settle on the Kronos X chain like
every other SECURITY fill (section 6i).

### Valuation

The closing auction price is the DLT Market's daily valuation: the price the
Art 3 aggregate of Regulation (EU) 2022/858 and the daily register are measured
at. When a day has no closing print the previous valuation stands.

## 7. Account, positions, margin (private)

| Endpoint | Description |
|---|---|
| `GET /v1/account` | Balance, equity, unrealized PnL, IM (position + order margin), MM, available, `spotReserved` (cash promised to open spot orders), margin ratio, risk status, `assets` (base-asset holdings: total / reserved / free), and since revision 3.62 `stp` / `stpScope` (your self-trade prevention election, section 6). Since revision 3.70 a `EUR` asset row of a USD-settled venue carries `reference` `{rateCcy "USD", rate, rateDate, source "ECB", equivalent, label}`: the informational, non-binding USD equivalent of the row's `total` at the ECB euro reference rate (series EXR/D.USD.EUR.SP00.A, USD per 1 EUR, four decimals as published, `equivalent` = `total` times `rate` rounded half even to six decimals, one direction EUR to USD only). It is OMITTED (never null) on every other asset row and whenever the venue holds no rate from the last five calendar days (unless the venue publishes another cutoff); the EUR balance itself stays segregated and unconverted, and the WS `get.account` ack carries the identical block. `feeTier`, `makerFeeRate` and `takerFeeRate` are your account-wide tier rates on the perpetual, spot and barrier markets; on swap markets you pay the instrument rates (section 6h) |
| `GET /v1/account/margin` | Same payload, cheap alias for polling |
| `GET /v1/account/parameters` | Every parameter the venue applies to YOUR account, with the scope that supplies each value (revision 3.61, "Your effective parameters" below): the account-wide values, one row per listed market, your rate-limit and order-to-trade classes, your fee resolution, your margin-watch level. A `READ_ONLY` credential may call it |
| `PUT /v1/account/parameters` | Set or clear your OWN tighter limits (revision 3.64, "Your own tighter limits" below): `{ "symbol"?, "set": { name: value }, "clear": [ name ] }`, account-wide without `symbol` (`maxOpenOrders`, `imMultiplier`, `priceBandBps`, `marketMaxSlippageBps`, `maxOpenRfqs`, `maxOpenContracts`) or for one market with it (those plus `maxOrderNotional`, `maxOrderQty`, `maxPositionNotional`, `maxPositionQty`). A value of yours can only TIGHTEN what is in force (the market rule and any value venue operations set for your account); a looser one is `5003` with a sentence naming the rule; a cleared name reverts to the venue's rule. Answers what was written and the effective values afterwards. TRADE credential |
| `PUT /v1/account/stp` | Elect your self-trade prevention policy and scope (revision 3.62, section 6 "Self-trade prevention"; since revision 3.63 an optional `"symbol"` makes it your per-market election on that market, `INHERIT` removes it, and the answer carries `symbol`, `stpSource` and `stpScopeSource`): `{ "stp": CANCEL_RESTING \| CANCEL_TAKER \| CANCEL_BOTH \| REJECT_TAKER, "stpScope": END_CLIENT \| MEMBER }`, either field alone keeps the other; answers the pair now in force, which also reads on `GET /v1/account` and `GET /v1/account/parameters`. TRADE credential |
| `POST /v1/margin/preview` | What-if margin for a basket of 1 to 50 hypothetical orders, `{"orders":[{...same as place...}]}` → indicative `{accepted, im, mm, available}` for the WHOLE basket priced together (same one-sided max(buy, sell) netting per instrument as resting orders; spot prefunding needs summed per currency; `MARKET` entries valued at the mark). `accepted` applies the admission rules order entry applies, including the reduce-only budget, with the same `reduceOnly` exemptions (see below) |
| `GET /v1/positions?symbol=` | Member-level positions: net qty, avg entry, mark, uPnL, realized PnL, accrued funding, ADL quintile |
| `GET /v1/positions/end-clients?symbol=&clientUid=` | Per-`clientUid` sub-positions (informational, for your reconciliation). Hedge-mode buckets (section 6) are separate rows carrying `positionSide` (`LONG` \| `SHORT`, omitted on netting rows), so a client can have up to three rows per instrument |
| `GET /v1/reconciliation/end-client-positions?date=&format=` | The end-of-day snapshot of your end clients' sub-positions for one business day (`date` as `YYYY-MM-DD`; described here since revision 3.81, served since the first release), the record to reconcile your own per-client ledger against: one row per non-flat `clientUid`, symbol and `positionSide`, as the venue's history store held it when the venue's daily anchor ran, within about five minutes after 00:00 UTC for the day that just ended, so a fill of the first minutes of the new day can be in it. If the venue restarts, or another of its nodes takes over, before the next anchor, the anchor runs again for the previous day and adds the rows of the sub-positions that day does not hold yet, from the state at that moment, `mark` and `upnl` included (all of the day's rows when the anchor had not run at midnight); a snapshot venue operations take by hand does the same. Rows are immutable once written, a day without a snapshot answers an empty list, and the envelope `asOfSeq` is the store watermark. JSON (the default, and any `format` other than `csv`): `{date, clientUid, symbol, positionSide, netQty, avgEntry, mark, upnl}`, `positionSide` (`LONG` or `SHORT`) omitted on netting rows, `mark` omitted when the market had none, `upnl` omitted when the row holds none. `format=csv` answers `text/csv` WITHOUT the envelope: the header `date,clientUid,symbol,positionSide,netQty,avgEntry,mark,upnl`, then one line per row with `date` as `YYYY-MM-DD` (before revision 3.81 the CSV printed it month first, `MM/DD/YYYY`), `positionSide` `NET` on netting rows, an absent value as an empty field, and a field that holds a comma, a double quote, CR or LF quoted per RFC 4180 with every inner quote doubled; nothing else is escaped, so a spreadsheet may evaluate a `clientUid` that begins with `=` or `@`. `upnl` (since revision 3.81) is the unrealized PnL at `mark` in the market's settlement currency, exact to the micro: the `unrealizedPnl` `GET /v1/positions/end-clients` reports at that mark, computed from the booked cost basis, so it can differ by micros from `netQty` x (`mark` - `avgEntry`), whose `avgEntry` is a display figure. The venue writes it only where the history store's net quantity for the row equalled the matching engine's at the snapshot's instant; otherwise, without a mark, and on every row recorded before revision 3.81 (rows are never rewritten) it is empty in the CSV and omitted in JSON |
| `GET /v1/adl/indicator?symbol=` | Your ADL queue quintile (1 to 5; 5 = first to be auto-deleveraged). The queue ranks every holder on a side by unrealized profit ratio (unrealized PnL over the position's entry notional) multiplied by account leverage (position notional at the mark over your equity in the settlement currency), highest first, ties by ascending member id; a position without unrealized profit ranks below every profitable one (its score is the loss ratio divided by account leverage) and is reached only by the terminal backstop; the ranking is recomputed every 5 seconds; the full rule is stated below ("Auto-deleveraging ranking", revision 3.70) |
| `GET /v1/unwinds` | Liquidation/ADL episodes affecting you (as defaulter: full detail; as counterparty: your allocations). CLOSED episodes are retained live up to 10,000 venue-wide; beyond that the oldest closed episodes leave this endpoint and the WS `get.unwinds` twin. Older history stays available via the event stream (section 11), `GET /v1/adl/events?from=&to=&limit=` and your archived executions. Open episodes are never released |
| `GET /v1/adl/events?from=&to=&limit=` | Your auto-deleveraging allocations, newest first, from the projection store (revision 3.70 documents the route and its fields, served since the projections existed; `from` / `to` are Unix ms on `ts`, `limit` 1 to 1000, default 100; the envelope `asOfSeq` is the store watermark): `{tradeId, episodeId, symbol, price, qty, qtyLots, side, role, closeSide, positionSide, clientUid, ts}`. `side` is YOUR relation to the EPISODE (`DEFAULTER` on every row of an episode you were unwound in, your own per-client rows included; `COUNTERPARTY` on a slice taken from you); `role` is the row's attribution (`DEFAULTER`, the defaulter's own per-client allocation; `DELEVERAGED`, the counterparty allocation; on the defaulter's own rows both read `DEFAULTER`); `closeSide` (`BUY` \| `SELL`) is the direction the venue closed the member named by the row in, omitted on rows recorded before revision 3.70 (every one of them a counterparty row, `role` `DELEVERAGED`); `positionSide` is the hedge bucket, omitted for netting; `clientUid` is present on your own rows only (a deleveraged member's allocations, a defaulter's own per-client rows) and omitted on the slices a defaulter sees taken from OTHER members (no end-client identifier crosses members). Several rows per `tradeId` are normal: one per allocation. The same rows reach you live on the `adl` channel (section 10) and are cursor-backfillable (section 11) |
| `GET /v1/margin/calls?from=&to=&limit=` | Margin-call notice history: `{seq, cursor, ts, equity, im, mm, available, ccy}` per notice, newest first (section 11) |

**Migration table, revision 3.81** (dated 19 October 2026; the CSV date, the CSV
quoting and the omitted empty `positionEffect` are corrections in force at
publication by the operator's decision of 2026-10-19, the venue being before
production go-live, the 30-day notice of the versioning policy waived for them;
this table is the guide of what to do):

| What you do | Before revision 3.81 | From revision 3.81 | What to do |
|---|---|---|---|
| read the `date` column of `GET /v1/reconciliation/end-client-positions?format=csv` | month first, `MM/DD/YYYY` (`10/18/2026` for 18 October 2026), on every venue host | `YYYY-MM-DD` (`2026-10-18`), as the JSON form and the `date` parameter always were | parse the ISO date; a CSV you saved before keeps the month-first text |
| read a CSV line whose `clientUid`, or another field, holds a comma, a double quote, CR or LF | printed as it is, so a comma split the line into one field too many | the field wrapped in double quotes, every inner quote doubled (RFC 4180); nothing else is escaped | read the CSV with an RFC 4180 parser, never by splitting the line on commas |
| read `upnl` in the CSV or in the JSON rows | empty on every CSV row, no `upnl` member in JSON | the exact unrealized PnL at `mark` where the venue wrote one; empty in the CSV and omitted in JSON otherwise, rows recorded before revision 3.81 among them | treat an empty or absent `upnl` as absent, never as 0 |
| read `positionEffect` on `GET /v1/executions` for a fill that left your client's sub-position unchanged | `"positionEffect": ""` | the member omitted, as on the WebSocket execution frame and the event stream | read an absent `positionEffect` and `""` alike |

**Margin preview (`POST /v1/margin/preview`).** The `orders` array is priced as ONE
basket of 1 to 50 entries: the figures answer what your margin state would be with
EVERY entry working at once, on top of what you already hold, and the result does not
depend on array order.

- **How entries are priced.** A priced entry is valued at its own limit, a conditional
  at the limit it would work at once armed (`trigger.limitPrice`), and a `MARKET` entry
  at the current mark, on perpetual and spot markets alike. The mark is what the venue
  already values a market order at for the per-order size cap and the `minNotional`
  floor. It is NOT the price a market order would get: the protection collar forms from
  the book at the instant of entry, so a market entry is an indication at today's mark,
  not a quote. Size a market order against the book, or send a limit. On a SPOT market
  buy the difference is a funding one: order entry prefunds it at the collar, which is
  normally above the mark, so a spot market buy sized to the last micro of your cash can
  preview `accepted: true` and still be rejected `3001` when you send it.
- **What `accepted` answers.** Entry by entry, the same admission rules order entry
  applies: available (perpetual order margin, spot buy prefunding in cash, spot sell
  prefunding in base-asset units, summed per currency), the per-order size cap (2013),
  the `minNotional` floor (2028), the position cap (2011) and the reduce-only budget
  (2008).
- **Reduce-only entries carry the same exemptions they carry on order entry.** A
  `reduceOnly` entry is EXEMPT from the available gate, from `minNotional` and from the
  position cap, so a reduce-only basket previewed during a margin call answers
  `accepted: true`, which is what sending it does. It is NOT exempt from the per-order
  size cap: that cap guards closes too, so closing a large position may take several
  orders.
- **The reduce-only budget applies to the whole basket, and it is MEMBER-level.** A
  `reduceOnly` order may only close what your MEMBER book holds: order entry admits one
  while the reduce-only quantity already working on that market, plus the new order,
  stays within your member net position on the reducing side, and rejects the surplus
  with `2008`. The preview measures your entries together against the same budget, so a
  basket that would close MORE than you hold answers `accepted: false` rather than
  promising an acceptance the second entry would not get. An entry on the side that
  would ADD to your position, or on a market where you hold nothing, is rejected the
  same way. A `clientUid` on an entry never narrows the budget to that client's
  sub-position, here or on order entry: see the reduce-only warning in section 6, which
  matters most for an end-client close.
- **What it does not answer.** Venue state that is not a property of your basket: halts,
  member or instrument status, and whether a market order would find liquidity.
- **Entries the venue would refuse outright answer with a code, not with `accepted`.**
  `accepted` is for the admission gates above. An entry order entry would reject before
  it ever reaches them fails the preview with the same code the order gets: `2016` for
  `reduceOnly` or a market-type conditional BUY on a spot market, `2013` for an out of
  range trigger or post-trigger limit price, `3005` for a market with no sequenced mark
  yet, `2002` for a limit priced at zero or a misaligned value, `2018` for a `leverage`
  or `quoteCcy` assertion the instrument cannot satisfy. The basket answers with the
  first such entry.
- **It is a read.** Nothing is placed, nothing is reserved, no order budget is consumed,
  and a `READ_ONLY` credential may call it. The answer is computed against the state at
  `asOfSeq` and the market moves.

**Auto-deleveraging ranking (`GET /v1/adl/indicator`, revision 3.70).** The
quintile is a rank the venue computes per market and per side. Every position
scores its unrealized profit as a fraction of its entry notional (the
position's notional at its average entry price) multiplied by your account
leverage on that market (the position's notional at the mark divided by your
equity in the settlement currency); a position with an unrealized loss scores
that fraction divided by the leverage instead, so it ranks below every
profitable one. Positions are ranked by score, highest first, ties by the
lower member id, and `adlQuintile` is 5 minus the integer part of
rank x 5 / count with rank 0 at the top: the top of the ranking reads 5 and
the bottom of a ranking of five or more positions reads 1 (a side with fewer
positions never shows the lowest values). The ranking is recomputed about
every 5 seconds and not while the market is on a stale mark or has no mark
yet; a deleveraging uses the ranking most recently computed, so the order you
see is the order the episode uses. A deleveraging on an order-book market
closes candidates in that order at the defaulting member's bankruptcy price
(section 6), profitable positions first, each up to the quantity still needed,
then, only where the profitable side is insufficient, the remaining
opposite-side positions in the same order at the same price; on an RFQ market
the same ranked close is the liquidation step itself and executes at the mark
(section 6b).

**Your effective parameters (`GET /v1/account/parameters`, revision 3.61).** The
rules of every market are public on `GET /v1/instruments`. This read answers what
applies to YOU after every member-specific tightening, and only ever your own
values: no member sees another member's parameters. A member-specific value can
only ever TIGHTEN a market rule (a smaller cap, count or band, a larger margin
multiplier or withdrawal buffer); it is written by venue operations against an
objective published class, or as a measure with a stated rulebook ground and a case
reference that the venue notifies you of by mail, and you may appeal under Rules
section 36. The `withdrawalBuffer` row is the fraction of the amount withdrawn that
must remain on your account after the withdrawal, over and above Initial Margin
(with your multiplier, open orders that may increase a position included) and cash
or assets reserved for your spot orders, met from your cash balance after those
reservations and never from unrealised profit; the venue-wide value is published on
the Website and is 0; a higher value applies to you only as the published value of
a class you sit in or as a Rules measure with a ground and a case reference notified
by mail and appealable under Rules section 36 (Rules section 14); a withdrawal that
fails the test is refused and may be resubmitted for a lower amount, and
withdrawals stay refused during liquidation or default management (section 10).

```json
{
  "asOfSeq": 1234567890, "memberId": 2, "code": "SANDBOX2",
  "account": {
    "maxOpenOrders":        { "value": 10000, "source": "MEMBER" },
    "imMultiplier":         { "value": "1",   "source": "MEMBER" },
    "withdrawalBuffer":     { "value": "0",   "source": "VENUE" },
    "priceBandBps":         { "value": null,  "source": "MARKET" },
    "marketMaxSlippageBps": { "value": null,  "source": "MARKET" },
    "maxOpenRfqs":          { "value": 64,    "source": "VENUE" },
    "maxOpenContracts":     { "value": 256,   "source": "VENUE" },
    "stp": "CANCEL_RESTING", "stpScope": "END_CLIENT", "stpSource": "MEMBER", "stpScopeSource": "MEMBER",
    "rateLimits": { "class": "STANDARD", "readsPerMin": 6000, "ordersPerMin": 40000, "ordersPerSecBurst": 1000,
                    "source": "VENUE", "readsSource": "VENUE", "burstSource": "VENUE" },
    "otr": { "class": "STANDARD", "warn": null, "block": null, "windowMinutes": 60, "minOrders": 100,
             "source": "VENUE", "basis": "PER_INSTRUMENT", "state": "OK",
             "surcharge": { "enabled": false, "bps": null } },
    "dea": { "class": "STANDARD", "ordersPerDay": null, "notionalPerDay": null, "source": "VENUE",
             "usedToday": { "orders": 0, "notional": "0", "day": "2026-09-10" }, "stoppedIdentifiers": [] },
    "fees": { "tier": "STANDARD", "makerFeeRate": null, "takerFeeRate": null,
              "criteria": "every member: the instrument rates of the published schedule (measured 12500000 over 30 days)",
              "measuredAt": "2026-09-01T00:00:00Z", "volume30d": "12500000", "cycle": "2026-09", "pending": [] },
    "marginWatchPct": 80,
    "legacy": { "maxOrderQtyOverride": null }
  },
  "markets": [
    { "symbol": "BTC-USD-PERP",
      "access":               { "value": "ALLOWED", "source": "MARKET" },
      "sessionStatus":        { "value": "ACTIVE",  "source": "MEMBER" },
      "maxOpenOrders":        { "value": null, "source": "MARKET" },
      "maxOrderNotional":     { "value": "10000000", "source": "MARKET" },
      "maxOrderQty":          { "value": "10", "source": "MARKET" },
      "maxPositionNotional":  { "value": "2000000000", "source": "MARKET" },
      "maxPositionQty":       { "value": "100", "source": "MARKET" },
      "imMultiplier":         { "value": "1", "source": "MEMBER" },
      "priceBandBps":         { "value": 500, "source": "MEMBER_MARKET", "inert": [ { "source": "MEMBER", "value": 800 } ] },
      "marketMaxSlippageBps": { "value": 500, "source": "MARKET" },
      "maxOpenRfqs":          { "value": 64, "source": "VENUE" },
      "maxOpenContracts":     { "value": 256, "source": "VENUE" },
      "makerFeeRate":         { "value": "0.0002", "source": "INSTRUMENT" },
      "takerFeeRate":         { "value": "0.0005", "source": "INSTRUMENT" },
      "stp": "CANCEL_RESTING", "stpScope": "END_CLIENT", "stpSource": "SELF_MARKET", "stpScopeSource": "MEMBER",
      "eligibilityRestricted": true,
      "otr": { "orders": 12, "trades": 3, "ratio": 4, "state": "OK", "windowMinutes": 60 },
      "eligibility": { "restricted": true, "source": "MEMBER_MARKET",
                       "memberLayer": { "restricted": false, "version": 0 },
                       "marketLayer": { "restricted": true, "version": 2 } } }
  ]
}
```

- Since revision 3.63 every market row also carries `stpSource` and
  `stpScopeSource` (`MEMBER` the account policy, `SELF` a value you set under a
  self-service scope, `MEMBER_MARKET` a per-market value venue operations set at
  your request, `SELF_MARKET` your own per-market election on
  `PUT /v1/account/stp` with `symbol`) and `eligibility`, the Market Segments layers
  in force on that market (section 8): `restricted` is true when either layer
  narrows your counterparties there, `source` is `VENUE` (open to all), `MEMBER`
  (your account configuration) or `MEMBER_MARKET` (your per-market layer),
  `memberLayer` is your account configuration and `marketLayer` your per-market
  layer on that market (absent while you never wrote one). The account block carries
  `stpSource` and `stpScopeSource` for the account election.

- Every row is `{ value, source }`. `value` is WRITTEN even when null: null means
  "no rule at any scope" (a market with no per-market open-order ceiling, an
  account-wide band you do not have). `source` names the scope that supplies the
  value: `VENUE` (the venue value), `MARKET` (the market rule), `MEMBER` (a value
  venue operations set for your account on every market), `MEMBER_MARKET` (one set
  for your account on this market), `LEGACY` (the historical per-account quantity
  override, in contracts, also shown under `legacy`), and on the fee rows
  `INSTRUMENT` \| `TIER` \| `PROGRAMME` (on SWAP market rows always `INSTRUMENT`
  since revision 3.70, a stored tier or programme value shown under `inert`),
  and since revision 3.64 `SELF` (a value
  you set yourself on `PUT /v1/account/parameters`, account-wide) and `SELF_MARKET`
  (one you set yourself on that market), see "Your own tighter limits" below. The
  set is open.
- `inert`, when present, lists stored values a tighter scope beat, so what the venue
  stores for you and what is in force are both visible and never contradict the
  venue's audit trail. An account-wide band looser than one market's band is inert
  there; an `access` of `ALLOWED` stored on an OPEN market is inert until the market
  requires admission.
- `access` is `ALLOWED` or `DENIED` per market: on an `ADMISSION` market
  (`GET /v1/instruments accessMode`) you may open new risk only while `ALLOWED`;
  `DENIED` refuses new risk anywhere (reject `3009`, section 9). Reduce-only
  orders and barrier close requests stay admitted, and since revision 3.70 so do
  the writer's quote on a close request and the holder's acceptance of it (a
  close settles existing risk). `sessionStatus` is the most
  restrictive of your account status and a per-market overlay venue operations may
  apply (`ACTIVE` \| `POST_ONLY` \| `REDUCE_ONLY` \| `SUSPENDED`, reject `3002`
  beyond it); a `SUSPENDED` overlay cancels your resting orders on that market.
- `maxOpenOrders` per market is the market's ceiling or your own value on it;
  the account row is your account-wide cap (reject `2012`). `maxOrderNotional` /
  `maxOrderQty` and `maxPositionNotional` / `maxPositionQty` are the per-order and
  position caps in force for you on that market (`2013`, `2011`), the notional arm
  where the market carries one, the quantity arm otherwise: on a market that
  carries a notional cap the quantity row is the market's own cap with source
  `MARKET`, a quantity value written there is refused (`5003` on your own write,
  with a sentence naming the notional name that applies), and one stored earlier
  reads under `inert`. `imMultiplier` scales
  your initial margin on that market. `priceBandBps` and `marketMaxSlippageBps` are
  your own band and collar (`2003`, and the collar price your market orders fill
  within). `maxOpenRfqs` and `maxOpenContracts` cap your open requests and open
  barrier and swap contracts (`2012`, `2011`).
- `rateLimits`, `otr` and `dea` are the gateway-side controls of section 4, each
  with the published `class` you sit in and the values in force: `rateLimits`
  (per-minute rates, the per-second burst ceiling, one `source` per value:
  `VENUE` the STANDARD class, `CLASS` a published class, `MEMBER` a tighter value
  of your own; the example reads the production STANDARD class of the published
  ladder, `ordersPerSecBurst` 1000, the sandbox reads 0), `otr` (the thresholds in
  force, null = none; the published window
  and floor; since revision 3.70 `basis` `PER_INSTRUMENT` and the account-wide
  `state`, OK or WARN, a warning flag only: the block is decided per market on
  `markets[].otr`; the prepared `surcharge` row, not in force by venue decision and
  applied to nobody while `enabled`
  is false), `dea` (the DEA-flow limits in force, `usedToday`, and the identifiers
  stopped on your account, reject `3010`; `3011` at the flow limit).
- `markets[].otr` (revision 3.70, order-book markets only, absent on an RFQ-mode
  market) is your order-to-trade ring on that market over the published window:
  `orders` (new orders, batch entries, amendments and the cancels you asked for),
  `trades` (your ordinary fills there: continuous-trading executions and
  call-auction executions; liquidation, ADL and reversal executions are not
  counted), `ratio` (null without a trade), `state`
  (`OK`, `WARN` or `BLOCK`: what your next order message on that market gets;
  `BLOCK` answers `1004` there and nowhere else) and `windowMinutes`. Zeros and
  `OK` while nothing was counted on the market.
- `fees` is your fee resolution under the published schedule (revision 3.62;
  your tier and any programme apply on the perpetual, spot and barrier markets
  only, never on a swap market, section 6h):
  `tier` is the tier the published measurement cycle put you in (`STANDARD` = the
  instrument rates, `makerFeeRate` / `takerFeeRate` null), `criteria` the published
  criteria of that tier with the figure measured, `volume30d` your traded notional
  over the measurement window, `measuredAt` and `cycle` when. `fees` itself is
  never null, but its `measuredAt`, `volume30d` and `cycle` read null until the
  first measured fee cycle that includes your account, and so do `tier` and
  `criteria` unless you are enrolled on a Market Maker Scheme tier, which they
  then name with its obligations (the cycle runs at the first-of-month 00:00 UTC
  boundary, so a member admitted after the last cycle, and every member of a
  venue started after it, reads null until the next one, unless venue
  operations run a measurement before it); a null there means "not measured
  yet", never a fee of zero, and
  the fee deducted on each market is on every `markets[]` row, `makerFeeRate` and
  `takerFeeRate` with their `source`, whatever this block reads (revision 3.83
  states it; nothing changed). `pending` lists every
  rate change of the schedule that will touch you (your tier's, and every per-market
  programme you are enrolled in) with its `effectiveFrom`: a rate never changes
  before the published date. A member-market rate (`PROGRAMME` on the market rows)
  exists only as an enrolment in a published programme whose measured
  qualification you meet; the Market Maker Scheme tier only under a signed
  agreement. A tier rate applies on a market only where it is
  lower than that market's instrument rate, compared per side (revision 3.70): on
  such a market the row carries the instrument rate with source `INSTRUMENT` and
  your tier rate under `inert`, and that is the fee deducted there; where your
  tier rate is the lower one the row reads `TIER`; a negative maker rebate is
  lower than any positive instrument rate and always applies, so the maker side
  may read `TIER` while the taker side of the same market reads `INSTRUMENT`.
  Traded notional,
  the figure the tiers are measured on, is the executed price times the executed
  quantity in the settlement currency over every executed trade on the
  perpetual, spot and barrier markets where you are maker or taker, yield contract
  takes excluded, liquidation and ADL prints included, reversal legs counted and
  not netted, over the trailing 30 calendar day window, measured on the
  first-of-month 00:00 UTC cycle. `pending` also lists, as rows of kind
  `INSTRUMENT` (revision 3.70), the announced change of the default rates of a
  market where it moves what you pay there: every such change when you have no
  tier, and with a tier exactly the changes that move the lower of your tier rate
  and the market rate on at least one side (the per-side rule of `INSTRUMENT` and
  `TIER` above); a market a programme prices you on is never listed, nor one where
  your tier stays the lower rate on both sides before and after the change;
  every change of the published fee schedule, a tier or programme rate, a band or
  criterion, a scheme term, the activation of the order-to-trade surcharge, and the
  per-market default rates, is treated as a material amendment and announced at
  least seven Business Days ahead on the Website and here (revision 3.70), and the
  market row of `GET /v1/instruments` carries the same announcement as `pendingFees`. Since
  revision 3.70, for a member enrolled in the Market Maker
  Scheme, `criteria` ends with the scheme's published obligations, ` (obligations:
  presence >= X percent, spread <= Y bps, size >= Z lots over N days)`: your
  quoting presence on the enrolled order-book markets (the share of minute
  boundaries with a resting limit order on both sides, the median spread and the
  smallest per-side size over the window) is measured against them at enrolment
  and at every published cycle, and a member below them is withdrawn to the
  instrument rates with the section 36 notice. Where the scheme publishes a
  monthly rule (` ... on at least D UTC days per calendar month`, with an optional
  ` between HH:mm and HH:mm UTC` window), a per-market programme's or scheme
  tier's qualification is measured per UTC day against the obligations the venue
  publishes with it (presence, maximum own-quote spread, minimum size, window)
  and re-qualified every month; a missed month ends the enrolment at the next
  cycle. `marginWatchPct` is the utilisation level at which the venue sends you an
  early warning (0 = off): a `MARGIN_WATCH` advisory on the private `venueEvents`
  channel (section 10.9) and a mail to your trading contact.
- Changes to your own values arrive on the WebSocket `venueEvents` channel as
  `MEMBER_PARAMS` frames (section 10.9); re-read this route on each. The WS
  `get.instruments` read carries the same market fields as `GET /v1/instruments`.

**Your own tighter limits (`PUT /v1/account/parameters`, revision 3.64).** Beside
the values venue operations set, you may set your own pre-trade limits within the
venue's rules (RTS 7 Article 20, RTS 6 Article 15): TRADE credential, body
`{ "symbol"?, "set": { name: value }, "clear": [ name ] }`. Without `symbol` the
write is account-wide and may name `maxOpenOrders`, `imMultiplier`, `priceBandBps`,
`marketMaxSlippageBps`, `maxOpenRfqs` and `maxOpenContracts`; with `symbol` it is
for that market alone and may also name `maxOrderNotional`, `maxOrderQty`,
`maxPositionNotional` and `maxPositionQty` (quantities, notionals and the
multiplier as decimal strings, counts and bps as integers; the quantity names
only on a market WITHOUT the matching notional cap: on one that carries
`maxOrderNotional` a `maxOrderQty` value can bind nothing and is refused with
`5003` and `BTC-USD-PERP carries a notional order cap (maxOrderNotional applies
there): set maxOrderNotional instead`, the position twin likewise). The venue resolves the
TIGHTEST of the market rule, any value venue operations set for your account and
your own value, so a value of yours can only tighten what is in force: a looser
one is refused with `5003` and a sentence naming the rule, before anything is
written, for example `maxOrderQty 60 would loosen the value venue operations set
for your account on BTC-USD-PERP (50): a self-set value can never loosen the
venue's`, or `priceBandBps 1200 would loosen the market band 1000 on BTC-USD-PERP:
a self-set value can only tighten the rule in force, never loosen it` (the
multiplier is a maximum, so there a SMALLER value is the loosening). A per-market
value without `symbol` and a name that is not self-service (`withdrawalBuffer`,
`sessionStatus` and `access`, which are venue measures, and `stp` / `stpScope`,
which are `PUT /v1/account/stp`) are refused the same way; an unknown `symbol` is
`2001`. A cleared name reverts to the venue's rule; venue operations never edit
your own values; a value of yours that a later, tighter venue rule beats stays
stored and is listed under `inert`. The answer is `{ scope: ACCOUNT | MARKET,
symbol, set: [names], cleared: [names], parameters }`, `parameters` being exactly
the `GET` read afterwards. On the read a value of yours in force shows as `source`
`SELF` (account-wide) or `SELF_MARKET` (that market), and `self`
`{ account, markets: [ { symbol, values } ] }` lists every value you hold raw,
including one that merely ties the rule in force. Your other sessions receive a
`MEMBER_PARAMS` frame with `origin` `MEMBER` (section 10.9). The member portal's
"API and limits" page offers the same controls. Both `PUT /v1/account/parameters`
and `PUT /v1/account/stp` are metered on the READ bucket of section 4 (reference
data, not order entry, so they never consume an order-budget unit): `1003` with
`retryAfterMs` once your read budget is spent, as on every private read. A body
with more than 15 entries in `set` or `clear`, or a parameter name over 64
characters, is refused with a fixed sentence (`5003`) before anything is read.

```json
PUT /v1/account/parameters
{ "symbol": "BTC-USD-PERP", "set": { "maxOrderNotional": "250000", "maxPositionNotional": "500000" }, "clear": ["priceBandBps"] }

{ "code": 0, "msg": "ok", "asOfSeq": 1234567901, "ts": 1785000000700,
  "data": { "scope": "MARKET", "symbol": "BTC-USD-PERP", "set": ["maxOrderNotional", "maxPositionNotional"], "cleared": ["priceBandBps"],
            "parameters": { "asOfSeq": 1234567901, "memberId": 2, "code": "SANDBOX2", "account": { "...": "..." },
                            "markets": [ { "symbol": "BTC-USD-PERP", "maxOrderNotional": { "value": "250000", "source": "SELF_MARKET" }, "...": "..." } ],
                            "self": { "markets": [ { "symbol": "BTC-USD-PERP", "values": { "maxOrderNotional": "250000", "maxPositionNotional": "500000" } } ] } } } }
```

**Margin model (summary).** Margin is computed and enforced at **member level only**,
cross-margined per settlement currency. Initial and maintenance margin use bracketed
risk tiers (each notional slice pays its tier's rate, see `GET /v1/instruments`).
Orders reserve margin at their worst-case price, netted one-sided per instrument
(max of buy side, sell side). Equity below maintenance margin triggers the
liquidation waterfall: order cancellation, then paced position reduction through the
book, each tranche a market order for the tranche size published for the market
(`liquidation.slicePercent` of the remaining position on `GET /v1/instruments`), never
below the published minimum tranche (`liquidation.minSliceQty`) and never more than the
remaining position, price banded around the current mark by the published band
(`liquidation.aggressionBps`) in the interest of orderly execution (on the wire it is
an immediate-or-cancel limit order at the mark plus or minus the band); any part of a
tranche not executed within the band is cancelled and the next tranche is sized from
the remaining position at the next liquidation check; and, last resort,
auto-deleveraging at the bankruptcy price. ADL closes are fee-free on both
legs, in the classic case and in the one-step close of a liquidated member's
position on an RFQ-mode market: no maker, taker or other fee is charged on an
`ADL`-flagged execution, and the only ledger entry it books on either side is
the realized profit or loss (kind `ADLSETTLEMENT`). Auto-deleveraging
counterparties are taken in the queue order stated beside
`GET /v1/adl/indicator` above (unrealized profit ratio multiplied by account
leverage, highest first, ties by ascending member id). This MTF operates no
insurance fund, so a defaulter's residual books to the venue. Candidates are
drawn one at a time in ranking order; a selected member's position is closed
up to the quantity still needed before the next candidate is touched.
Auto-deleveraging reaches you only if you hold the opposite side of the
defaulter's market; profitable positions are closed first in deleveraging
order, and only where they cannot absorb the whole position does the venue
close the remaining opposite-side positions, including positions not in
profit, at the same bankruptcy price. An auto-deleveraging close is credited
to the counterparty final and immediate at the close-out price; nothing is
withheld against the defaulting member's negative balance, which the venue
claims separately. A member whose positions were closed in another member's
auto-deleveraging may later receive a `RECOVERYDISTRIBUTION` ledger entry
(revision 3.30): a pro rata share, by the notional of the positions closed
against it at the close-out price, of an amount the venue later recovers from
the defaulted member, capped in aggregate at the amount by which the close
reduced the participants' realized profit against the mark, with `refId` = the
episode id.
In extreme conditions a venue-generated close (ADL, the one-step liquidation
close on an RFQ-mode market, final settlement of a delisted instrument)
executes at the nearest representable price rather than the raw bankruptcy or
operator price: the venue caps the close price so the settlement amount stays
exactly representable, both legs of the close settle at the same capped price,
and final settlement applies one uniform effective price to every sub-position
on the instrument. No position of tradable size approaches the cap.
Automatic liquidation DETECTION pauses while any market the member holds a
position, an open order or barrier exposure on is stale (the staleness
reduce-only overlay, section 10.9) or has no mark price yet; the breach
re-detects and the episode starts on the first margin sweep after the market
recovers. A liquidation ordered by venue operations is not gated by this pause.
Every perpetual publishes HOW its funding is scheduled in the `funding` block on
`GET /v1/instruments` and `GET /v1/funding/current` (also on the WebSocket
`get.instruments` and `get.funding` reads): `schedule` is `GRID` or `LOCAL_DAILY`,
`nextSettlementTs` is the next settlement (the same instant as the legacy
`nextFundingTs`), and the block is omitted on spot and barrier markets, which
never fund. The set of `schedule` values is open; parse defensively.
`funding.rateCap` never exceeds 1 and `funding.rateFloor` never lies below -1
(revision 3.70): the venue refuses to list or reconfigure a perpetual outside
that band, so the per-market caps in force are Trading Parameters under the
Contract Specifications and this is a standing venue-side limit on how far they
can ever be amended.

**GRID (the crypto perpetuals).** Funding settles at fixed UTC anchors every
`intervalHours` (also the top-level `fundingIntervalHours`) at the rate in force
(the published `fixedRate` on a market whose `funding.rateSource` is `FIXED`,
else the last sequenced tick rate) and the last sequenced mark; positive rate
means longs pay shorts. Funding settles EVERY elapsed anchor: after a venue
outage spanning several anchors you receive one `FundingSettled` event and one
funding ledger entry per missed anchor, each computed at the rate in force and
the last sequenced mark, at most 8 anchors per settlement with any remainder
following on subsequent timer ticks about 30 seconds apart, and
`GET /v1/funding/history` row counts reflect every settled anchor. Each
history row carries `settleTs`, the anchor settled, and (revision 3.70)
`settledTs`, the time of the settlement command: equal to `settleTs` plus the
timer latency in steady state, later than it for an anchor settled after a
stale-mark deferral or a venue outage, and the same on every row of one
catch-up command; rows recorded before revision 3.70 omit it.

**Funding on a stale mark waits (revision 3.70).** Funding never settles on a
mark the venue holds as stale. While a market is in reduce-only for
`STALE_MARK_PRICE` (section 10.9), or its last mark is older than the market's
staleness threshold (`staleMarkTimeoutSec` on `GET /v1/instruments`), due
anchors wait (`nextSettlementTs` and `nextFundingTs` stay at the waiting anchor)
and are settled at the first settlement after the market leaves reduce-only,
normally within 30 seconds of the `RESUME_TRADING` event with reason
`MARK_PRICE_RECOVERED`, at most 8 per settlement, each at the then rate in
force and last sequenced mark on the positions held at that settlement, with
the anchor time as `settleTs` and the settlement time as `settledTs` on
`GET /v1/funding/history`. The catch-up after a venue outage longer than the
staleness threshold follows the same rule: it lands once the mark feed is back
and the market is fresh again, not at the restart itself, and the catch-up
fence below stays up until it has landed. Previously a due anchor settled at
the last sequenced mark whether or not the market was stale. LOCAL_DAILY
markets are unchanged: a day whose window recorded no mark skips and is never
caught up. Dormant on the sandbox until the operator decision batch epoch
announced by venue operations; live on production from the first day.

**Catch-up fence (revision 3.70).** Every settlement of a missed anchor is priced
on the positions held when it lands, so while a GRID market's settlement is more
than 60 s overdue the venue refuses NEW risk on that market until the catch-up
has landed: an opening order, a non-reduce-only amend and an RFQ accept answer
`3014` (section 6, the same text on every transport), reduce-only orders stay
admitted, and a conditional order whose trigger fires inside that window is
canceled with `cancelReason` `FUNDING_CATCH_UP` instead of executed. "Opening"
in that text means every order that does not carry `reduceOnly`: a client-level
close sent without the flag, the default of the lot close (section 6e), is
refused too (stated since revision 3.80; the rule is unchanged). The fence is
uniform per market, never per member, and it only ever bites while the venue is
behind on a settlement: after a restart the venue lands the catch-up BEFORE it
serves any member (the settlements are priced on the positions held at the
outage), and while an anchor is overdue it re-fires the settle every second, so
the window normally lasts seconds. The lift signal is the settlement itself: the
`FUNDING_SETTLED` event on your private stream (one per settled anchor) and the
`settlement` frame on the public `funding.{symbol}` channel, after which
`GET /v1/funding/current` `nextFundingTs` lies in the future again. On a market
whose mark is stale the deferral above holds the settlement back, so its opening
flow reads `3014` rather than `3002` once the anchor is more than 60 s overdue;
both refuse, and both drop with the catch-up. Dormant on the sandbox until the
operator decision batch epoch announced by venue operations; live on production
from the first day.

**LOCAL_DAILY (the equity index, gold, silver, oil and FX perpetuals, revision 3.42).**
One settlement per calendar day, keyed to a local wall-clock window in the
market's `zone` (`UTC`, `LONDON` or `NEW_YORK`; the venue applies daylight
saving, you do not). The rate is not taken from any feed; the venue computes it
from its own marks and index:

1. At `snapshotLocalTime` the venue captures every open position on the market
   (member net and per-client sub-positions) and the index price at that
   instant. `nextSnapshotTs` in the block is the next such capture (Unix ms).
2. Over `vwapWindow` (`HH:MM-HH:MM` local; it may straddle the snapshot time)
   the venue records one mark per minute: `vwapSnapshots` of them, the last
   sequenced mark at or before each minute boundary counted back from the
   window end. Minutes with no mark are skipped.
3. At the window END (`nextSettlementTs`) it settles
   `rate = clamp((mean of the recorded marks - indexAtSnapshot) / indexAtSnapshot, floor, cap)`,
   positive meaning longs pay, applied to the SNAPSHOT positions with the mean
   as the settle mark (`markPrice` on the settlement). The settle mark is the
   mean rounded half-even to the tick (`markPrice`, and `settlementValue` on
   the history row), while the rate is computed from the unrounded mean
   (revision 3.83 states it; nothing changed): reproduce the day's charges from
   the published `fundingRate` and `markPrice` as they stand ("Reproducing a
   funding charge" below). A position closed after
   the snapshot still pays that day; one opened after it pays nothing until the
   next day's snapshot. `fundingIntervalHours` reads 24 on these markets and
   `fundingRateAnnualizedPct` annualises over one day.
4. A day on which the venue held no position snapshot (an outage across the
   snapshot time) or recorded no mark in the window is skipped, never charged
   later. Only marks sequenced INSIDE the window feed the mean (revision 3.47:
   a mark from before the window never stands in for a minute the feed missed,
   so a feed that stopped before the window skips the day), and on the
   schedules whose snapshot time is the window end (oil, the equity indices)
   the venue takes the snapshot at most 60 seconds after the end; an outage
   longer than that across the snapshot time skips the day, never captures the
   positions as of the restart. The omission is shown in the funding history
   on request (revision 3.70): `GET /v1/funding/history?includeSkips=true`
   returns the day as a row with `skipped` true and `skipReason`
   (`NO_POSITION_SNAPSHOT`, `NO_MARK_SNAPSHOTS`; an open set), `settleTs` and
   `snapshotTs`, no rate and no mark, interleaved newest first with the
   settled rows; absent by default, and no funding is ever exchanged for it.
5. On LOCAL_DAILY markets funding settles once every calendar day, including
   days on which the underlying market is closed. On such days the venue mark
   and index carry the last available values of the venue's reference price
   source, and the settled rate is bounded by the published cap and floor
   (`funding.rateCap` and `funding.rateFloor` on `GET /v1/instruments`).

The listed schedules today: FX pairs and gold settle on London time (snapshot
16:00, window 15:57 to 16:03, six snapshots; since revision 3.75 silver and the
USD/JPY quanto too); WTI on New York time (snapshot
14:30, window 14:25 to 14:30, five); Brent on London time (snapshot 19:30,
window 19:25 to 19:30, five); the equity index perpetuals on New York time
(snapshot 16:00, window 15:55 to 16:00, five). Read the live values from the
block rather than hard-coding them.

On a LOCAL_DAILY market there is NO rate in force between the daily settlements
(the day's rate is computed at the snapshot from the index VWAP methodology and on the `settlement` frame and in `GET /v1/funding/history`), so
`fundingRate`, `fundingRatePct`, `fundingRateAnnualizedPct` and
`fundingPerUnit` are OMITTED there (revision 3.48; on `GET /v1/funding/current`,
`GET /v1/ticker` and the WebSocket `funding.{symbol}` rate frame): read the figures below for the day ahead and the settlement figures for what was. On a LOCAL_DAILY market `predictedFundingRate`, `predictedFundingRatePct` and
`predictedFundingPerUnit` (on `GET /v1/funding/current`, `GET /v1/ticker` and
the WebSocket `funding.{symbol}` rate frame) are the rate the marks recorded so
far would settle at, and they are OMITTED, not null and never a fabricated
zero, before the day's window opens. `GET /v1/funding/history` rows on these
markets additionally carry `settlementValue` (the mean the day settled at),
`indexAtSnapshot`, `snapshotsUsed` and `snapshotTs`, and the WebSocket
`funding.{symbol}` channel emits a `type: "settlementDetail"` frame with the
same figures right after each `settlement` frame. GRID rows and frames carry
none of these.

**Funding units: the rate is a fraction, the payment is money.** `fundingRate`
is a dimensionless fraction of position notional **per interval**, never a
currency amount: `"0.0001"` means 0.01% per interval, so a 100,000 USD long pays
10 USD at each settlement while it holds. Every surface that publishes a rate
also publishes `fundingRatePct` (the same number scaled by 100) and
`fundingRateAnnualizedPct` (rate x (365 days / `fundingIntervalHours`) x 100,
simple, no compounding, for comparison against other venues; OMITTED, not null
and never `0`, where a market has no funding grid). The currency-denominated
figure is the **payment**, booked in the instrument's settlement currency:

| You want | Read |
| --- | --- |
| The rate in force | `GET /v1/funding/current`, `fundingRate` / `fundingRatePct` (GRID markets; omitted on LOCAL_DAILY, whose rate exists only at settlement) |
| Historical rates | `GET /v1/funding/history` |
| Days that skipped (LOCAL_DAILY, no funding exchanged) | `GET /v1/funding/history?includeSkips=true`, the rows with `skipped` true |
| What a position has PAID | `accFunding` on `GET /v1/positions` (member) or `GET /v1/positions/end-clients` (per client) |
| The cash movements | `GET /v1/account/ledger?kind=FUNDINGPAY` and `FUNDINGRECEIVE` |
| What one lot has paid | `accFunding` on `GET /v1/lots` (section 6e) |

Settlement currency is a property of the **instrument**, not of the venue, so
every surface carrying a funding amount now also carries `settleCcy`. Today every
listed market settles in USD; do not hard-code that.

**Reproducing a funding charge; `fundingPerUnit` is for display (revision 3.83
states both; nothing changed).** The venue books funding once per settlement on
your MEMBER net position and attributes it to each client sub-position (one per
`clientUid` and hedge bucket, section 6). Every booked figure is reproducible
exactly, in decimal arithmetic, from the settlement's `fundingRate` and
`markPrice` (on `GET /v1/funding/history`, the `settlement` frame of the
WebSocket `funding.{symbol}` channel and the `FUNDING_SETTLED` event):

1. The member charge is `netQty x markPrice x contractMultiplier x fundingRate`,
   with `netQty` your signed member net position at the settlement (negative
   when short; on a LOCAL_DAILY market the snapshot position), rounded half-even
   to 0.000001 of the settlement currency (one micro; a tie goes to the even
   micro, symmetrically for a negative product). Positive means you pay: the
   ledger books it as `FUNDINGPAY` with the opposite sign, a negative charge as
   `FUNDINGRECEIVE`.
2. Each client sub-position's charge is the same product on that
   sub-position's own `netQty`, rounded the same way. When your member net
   position is 0 at the settlement (on a LOCAL_DAILY market, at the snapshot),
   nothing is booked: no member charge, and no client sub-position is charged,
   whatever the sub-positions hold (clients on opposite sides, or one client's
   `LONG` and `SHORT` buckets).
3. The member charge minus the sum of the sub-position charges (of either sign,
   up to about half a micro per client sub-position: with many sub-positions it
   can reach hundreds of micros) is added to the charge of the sub-position with
   the largest absolute `netQty` (between equal sizes, the `clientUid` the venue
   registered first, then `NET`, `LONG`, `SHORT`), so the per-client
   `accFunding`, taken over every sub-position that ever held on the market,
   sums exactly to your member `accFunding`. `GET /v1/positions/end-clients`
   lists open sub-positions only (and `GET /v1/positions` omits a market where
   your member net is 0), so the figures the two routes show agree only while
   your member net is not 0 and no sub-position on the market has gone flat;
   a flat sub-position's booked `accFunding` stays readable as the envelope's
   `accFunding` on `GET /v1/lots?clientUid=&symbol=` (section 6e).

`fundingPerUnit` and `predictedFundingPerUnit` are display figures, NOT the
figure to multiply (the sentence of revision 3.32 that told you to multiply it
by position size is corrected): each is the charge of ONE `qtyStep` (one lot)
at the mark, rounded half-even to the micro, divided by the `qtyStep`. So
`qty x fundingPerUnit` can differ from the booked charge by up to half a micro
for every `qtyStep` held, and the figure reads `0` while a charge is booked
when one lot's charge is at most half a micro. Example, SOL-USD-PERP (`qtyStep`
`0.01`) at a mark of `76.37` and a rate of `0.0001`: one lot's charge is
0.00007637 USD, rounded to 0.000076, so `fundingPerUnit` reads `0.0076`; a
long of 10,000 SOL is booked 76.37 USD (10000 x 76.37 x 0.0001), where
10,000 x `0.0076` gives 76.00. On `GET /v1/funding/history` a row's
`fundingPerUnit` is computed when you read it, from the row's `fundingRate`
and `markPrice` and the market's listing at that moment; the stored
`fundingRate` and `markPrice` never change. Reconcile against the booked
figures (`accFunding` and the `FUNDINGPAY` / `FUNDINGRECEIVE` ledger rows), or
recompute them by the three steps above.

## 8. Market Segments (private)

**Market Segments** is the venue's counterparty-acceptance mechanism (liquidity
pools): you may restrict which members you are willing to trade with. Matching
requires **mutual** acceptance; the default is open-to-all. Restricted liquidity
remains visible in the full book, the *matchable* book shows what you can
actually hit. Market Segments apply identically on perp and spot markets, and
venue operations can administer a member's configuration on request.

> Terminology note: "Market Segments" here means counterparty acceptance pools,
> not the ESMA/FIRDS "segment MIC" concept. Regulatory filings never use this term.

| Endpoint | Description |
|---|---|
| `GET /v1/market-segments` | Your config: `{restricted, allow: [memberCode...], version}`; since revision 3.63 also `markets: [{symbol, restricted, allow, version}]`, every per-market layer you hold (a market you never wrote is absent there) |
| `GET /v1/market-segments?symbol=` | Revision 3.63: your PER-MARKET layer on that market, `{symbol, restricted, allow, version}` (`version` 0 and `restricted` false while you never wrote one); unknown symbol `2001` |
| `PUT /v1/market-segments` | Replace your config (optimistic concurrency via `version`; conflict → code `4001`) |
| `PUT /v1/market-segments?symbol=` | Revision 3.63: replace your per-market layer on that market with the same body (send the layer's own `version`); answers `{version, symbol}` |
| `GET /v1/market-segments/matchable-members` | Member codes you can currently match with (the account configurations of both sides) |
| `GET /v1/market-segments/matchable-members?symbol=` | Revision 3.63: the member codes you can match with ON THAT MARKET (account configurations and market layers of both sides) |
| `GET /v1/orderbook/matchable?symbol=&depth=` | Your matchable book: rows are `[price, matchableQty, ownQty]` (per market, both layers of both sides since revision 3.63) |

Compatibility aliases (identical handlers and payloads): `GET/PUT /v1/eligibility`,
`GET /v1/eligibility/matchable-members`. The WS private channel keeps its
`eligibility` name.

**Per-market layers (revision 3.63).** Beside your account configuration you may
hold one layer per market. A layer only ever NARROWS: on a market you match a
counterparty only when your account configuration and theirs admit each other
AND your layer on that market and theirs admit each other (an absent or
unrestricted layer admits everyone the account configuration admits). The
account configuration and each layer carry their own `version`; a stale one
answers `4001` and changes nothing. The `eligibility` WS frame carries `symbol`
when a per-market layer changed and no `symbol` for the account configuration.
`GET /v1/account/parameters` reports the layers in force on every market row
(`eligibility`, section 7). Venue operations edit a configuration only as a
Rules measure (default management under section 35 or a sanction under section
36), notified to you by mail with the section 36 appeal route; the venue applies
no discretion of its own to whom you accept.

Note on execution prices: price-time priority is honored **within your accepted
counterparty set**. If better-priced liquidity exists from members outside your
mutual acceptance, your order executes at the best price *matchable for you*.

## 9. Error codes

The table below is the quick reference. The COMPLETE code reference, covering
every numeric code with client handling guidance, the WebSocket close codes, and
every enumerated string value the API returns (order statuses, cancel reasons,
venue event types and reasons, delivery and store states), is the companion
document `KronosX-API-Codes.md` / `.html`, revision-controlled alongside this one.

| Code | Meaning | HTTP |
|---|---|---|
| 0 | OK | 200 |
| 1001 | Authentication failed (since revision 3.85 also a WebSocket `auth` frame that does not name and sign its connection's challenge nonce, section 10.1) | 401 |
| 1002 | Timestamp outside recv window; since revision 3.85 also an `X-RECV-WINDOW` that is not an integer from 1 to 60000, a stamp more than 5000 ms ahead, a signed write already received (sign every send afresh), a signed write stamped before the node began to accept writes and one stamped at or before the oldest write the node still records (section 3) | 401 |
| 1003 | Rate limit exceeded | 429 |
| 1004 | Order-to-trade ratio limit exceeded (venue RTS 9 regime) | 429 |
| 1005 | Key not permitted: a `READ_ONLY` (drop copy) credential sent a write (order entry, cancel, amend or a configuration write), refused on every transport before the engine is reached; use your TRADE credential (section 3) | 403 |
| 2001 | Unknown symbol | 400 |
| 2002 | Price not on the tick grid, or qty not an exact multiple of `qtyStep`: a decimal numeral the grid cannot admit (zero, negative, finer than or off the grid, out of range), or a required price missing or empty. A string that is not a decimal numeral at all is `2013` since revision 3.60 | 400 |
| 2003 | Price outside band: farther than `priceBandBps` from the CURRENT mark, checked on a `LIMIT` order's price at placement and on amend (and at RFQ accept against the current mark); never on a conditional order's trigger `price` or `limitPrice`, at placement, on amend or at activation (section 6, revision 3.83). Derive resting-order offsets from `priceBandBps` and a live mark at runtime, never a hard-coded price: against live marks a fixed offset drifts out of the band and produces intermittent 2003s that look random | 400 |
| 2004 | Unsupported order type (incl. trailing stop) | 400 |
| 2005 | Post-only order would cross | 400 |
| 2006 | FOK unfillable | 400 |
| 2007 | No eligible liquidity (market order) | 400 |
| 2008 | Reduce-only violation | 400 |
| 2009 | Duplicate `clOrdId` with different payload (since revision 3.85, from the matching engine's replay-contract line for that release, also ANY reuse of a `clOrdId` on an RFQ request or quote, section 6b, with `msg` `clOrdId already used: an RFQ request or quote is never restated, read GET /v1/rfq/mine or GET /v1/rfq/quotes/mine`) | 400 |
| 2010 | `clientUid` required | 400 |
| 2011 | Position cap exceeded: worst case valued at the current mark exceeds `maxPositionNotional`, or exceeds the base-unit `maxPositionQty` where no notional cap is set (section 2a) | 400 |
| 2012 | Too many open orders: the member-wide cap on working plus untriggered conditional orders across ALL instruments, default 10,000. Your live value is on the member portal limits card and on `GET /v1/account/parameters`; a cap above the default is only ever a published class assignment (section 4), a member-specific value only ever tighter. Also the reject for the RFQ caps (64 open RFQs per member, 64 live quotes per request) | 400 |
| 2013 | Malformed order (an unknown `side` or `tif` word, a missing or misplaced `trigger`, an oversized field, a body that parses to null, and since 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`; and since revision 3.72 a `clOrdId`, a `clientUid` or a Trader ID of the `reg` block that begins or ends with whitespace, section 2), or order-size cap exceeded: where the market publishes `maxOrderNotional`, a single order, amend (full amended quantity), RFQ or quote whose `price x qty` exceeds it (priced orders valued at the limit price, market orders and RFQ creation at the current mark, quotes at the quoted entry price; `reduceOnly` NOT exempt); where no notional cap is set, the base-unit `maxOrderQty` (section 2a) | 400 |
| 2014 | Order not found; since revision 3.86 also an amendment or a cancellation of a `GTD` order at or past its `expireTs`, which the venue then reports `EXPIRED` (section 6, `tif`) | 404 |
| 2015 | Self-trade prevented (reject policy) | 400 |
| 2016 | Not supported on spot markets (`reduceOnly`, `positionSide`, market-conditional buys) | 400 |
| 2017 | Unknown field in the request body (never silently discarded), or a value of the wrong JSON type in a strictly parsed body (`PUT /v1/orders/{orderId}`, `POST /v1/orders` and every batch entry: `msg` "malformed request body"); since revision 3.84 also any request body on `DELETE /v1/orders`, and an args member of the WebSocket `order.cancelAll` other than `symbol` and `side` (section 6, "Manage orders") | 400 |
| 2018 | Order-level assertion (`leverage` / `quoteCcy`) does not match the instrument | 400 |
| 2019 | Malformed backfill cursor or unknown channel family. Since revision 3.83 a well-formed 30-digit cursor whose `seq` or `eventIndex` field lies above its range (section 11.2) answers it with a `msg` naming the field and its range | 400 |
| 2020 | `clOrdId` uses the reserved `KX-` namespace | 400 |
| 2021 | `reg.traderId` missing or not a registered Authorised User | 400 |
| 2022 | RFQ market: no order book, trade via `POST /v1/rfq` | 400 |
| 2023 | Not an RFQ market: use the order book | 400 |
| 2024 | RFQ not found, closed or expired | 404 |
| 2025 | RFQ quote not found, withdrawn or expired | 404 |
| 2026 | RFQ self-trade (quote or accept against your own member) | 400 |
| 2027 | Barrier level or expiry invalid for this market and side (also a take profit on the wrong side, a close-request quote outside the contract's terms, and since revision 3.45 a strict take-profit request without a take profit, without an expiry, with an expiry more than 24 h out, or on a market that does not offer it) | 400 |
| 2028 | Order or RFQ notional below the instrument's `minNotional` (section 2a) | 400 |
| 2029 | Venue end-client identifier capacity reached: this order or RFQ acceptance would register a clientUid the venue has never seen. Identifiers already known are unaffected | 400 |
| 2030 | Client identity submission invalid (section 6d): field presence per client type, ISO 3166-1 alpha-2 country, `YYYYMMDD` date of birth or ISO 17442 LEI checksum failed; the message names the offending field | 400 |
| 2031 | No client identity record on file for this `clientUid` (section 6d) | 404 |
| 2032 | No open lot with this `lotId` for the given `clientUid` and symbol (section 6e) | 404 |
| 2033 | `reg.tradingCapacity` missing, not a recognised or permitted value, or inconsistent with `clientUid` (section 6f). Answered only when capacity enforcement is at the ENFORCE stage for your member; the value must be `DEAL`, `MTCH` or `AOTC` and permitted by your licensing profile, `DEAL` requires `clientUid` absent or your house reference, `MTCH` and `AOTC` require a real end-client `clientUid` | 400 |
| 2034 | No licensing profile configured for your member (section 6f). PRODUCTION only: the production venue refuses order acceptance (order entry, batch and lot close, order amend on REST, WS and FIX 35=G, RFQ create, quote and accept) until venue operations configure your licensing profile, which is a mandatory onboarding artifact. The sandbox never answers this code | 403 |
| 2035 | `closeContractId` does not name an OPEN barrier contract you hold on this market, or a close request for it is already open (section 6b, early close by RFQ). Also answered at accept when the contract closed between the steps | 400 |
| 2036 | Swap offer terms outside the market (section 6h): `rateBps`, `tenorDays`, `qty` against `minNotional` / `maxOrderQty`, `minQty` above `qty`, validity 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` instead) | 400 |
| 2037 | Swap markets have offers and takes, never quotes: a quote on a swap offer, or a swap accept carrying a `quoteId` (section 6h) | 400 |
| 2038 | Swap take size not admissible: above the offer's `remainingQty`, above your `maxOrderQty`, or below the minimum take without being the whole remainder (section 6h; a zero or off-grid `qty` is `2002` instead) | 400 |
| 2039 | Barrier accept off the market (section 6b): the accepted quote's entry lies farther than the market's `barrierGuard.entryBandBps` from the CURRENT mark, or the mark is not live; the request and the quote stay open | 400 |
| 2040 | Barrier accept: the request's `takeProfit` is already reached by the CURRENT mark (section 6b); the request and the quote stay open | 400 |
| 2041 | Barrier accept: the `takeProfit` lies closer to the accepted entry than the market's `barrierGuard.minTakeProfitBps` (section 6b); the request and the quote stay open | 400 |
| 2042 | Barrier accept: the `barrier` lies closer to the accepted entry than the market's `barrierGuard.minBarrierBps` (section 6b); the request and the quote stay open | 400 |
| 2043 | Barrier accept: the `barrier` is already reached or crossed by the CURRENT mark (section 6b); the request and the quote stay open | 400 |
| 2044 | Barrier quote `premiumBps` outside the allowed range: below the market's effective share for the request's variant or above 10000 (section 6b, revision 3.58) | 400 |
| 2045 | `clOrdId` collision (revision 3.59, in force from the date venue operations announce on each environment): 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; nothing was sequenced, send it again with another `clOrdId` | 400 |
| 2046 | Auction phase (section 6j): an order type the market's current `tradingPhase` does not admit, an IOC, FOK, MARKET or conditional order on a SECURITY market during a call or while CLOSED; GTC and GTD LIMIT orders rest for the next call, cancel and amend work | 400 |
| 2049 | Token restricted (section 6i, revision 3.70): an order, an amend, an RFQ request, a quote or an accept on a SECURITY market whose DLT financial instrument is frozen on the Kronos X chain, or for a client whose custody wallet there is frozen by the venue registrar; answered before the engine on REST, WebSocket and FIX alike, your other clients and markets are unaffected | 400 |
| 3001 | Insufficient margin (perps) / insufficient quote funds (spot buys) | 400 |
| 3002 | Member status forbids action | 403 |
| 3003 | Instrument halted | 400 |
| 3004 | Insufficient base-asset balance (spot sells; swap takes: principal plus taker fee in the asset) | 400 |
| 3005 | Instrument has no mark price yet (wait for the feed; not a halt) | 400 |
| 3006 | Instrument delisted (permanent for that listing; not a halt; after a re-listing the same symbol can return as a new market, section 2a) | 400 |
| 3007 | Counterparty margin: the quoting member no longer passes margin or limits (RFQ accept); on a swap take, the writer is no longer fit (suspended, reduce-only, in liquidation or default, blocked by a claim, or whose Market Maker designation ended: the offer is withdrawn) or at its open-swap or contract-count caps for your size (3007 only, the offer stays on the board) | 400 |
| 3008 | Swap writer blocked: you may not post a swap offer while a contract you wrote carries an outstanding claim (section 6h) | 400 |
| 3009 | Market access (revision 3.61): your account may not open new risk on this market, because the market requires admission (`GET /v1/instruments accessMode` `ADMISSION`) and you are not admitted, or your access to it is `DENIED`; reduce-only orders and barrier close requests stay admitted, and since revision 3.70 so do the writer's quote on a close request and the holder's acceptance of it (a close settles existing risk). Your admission per market is `GET /v1/account/parameters markets[].access` (section 7). Since revision 3.70 the six swap markets (section 6h) are admission markets under the `SWAP_COUNTERPARTY` class; every perpetual, spot and barrier market is open | 400 |
| 3010 | DEA client blocked (revision 3.62): the `clientUid` or `reg.traderId` of this `reg.dea` order is an identifier venue operations stopped on your account (a Rules measure with a ground and a case reference, notified by mail with the section 36 appeal route). Your unmarked flow and your other DEA clients are unaffected; cancel the identifier's working orders yourself. On REST, the WS ack and the FIX report (`9026=Y`) alike, before anything is sequenced | 400 |
| 3011 | DEA flow limit (revision 3.62): your DEA-flow allowance for the calendar day (UTC), the count or the notional of `reg.dea` orders your class publishes, is reached; only `reg.dea` orders count and only they are refused. Resets at 00:00 UTC; the limits and today's use are `account.dea` on `GET /v1/account/parameters` | 400 |
| 3012 | Ledger finality pending (section 6i): a movement OUT of a trading wallet on the Kronos X chain (`POST /v1/chain/transfers`, `POST /v1/chain/unlock`) refused while the validator quorum of the latest block is not reached; trading and fills are unaffected. Retry when `GET /chain/v1/head` shows `unattestedTail: false` | 400 |
| 3013 | Swap writer role (revision 3.70): only a Member designated as a Market Maker Member may post a swap offer, and only on its own account (no `clientUid`, or your house uid); taking is open to every admitted member (section 6h). Answered once `GET /v1/instruments swapWriterEligibility` reads `MARKET_MAKER` on the market | 400 |
| 3014 | Funding catch-up (revision 3.70): this GRID perpetual's funding settlement is more than 60 s overdue and the venue is settling the missed anchors; opening orders, non-reduce-only amends and RFQ accepts are refused until the catch-up lands, reduce-only flow stays admitted, and a conditional order whose trigger fires inside the window is canceled `FUNDING_CATCH_UP`. Uniform per market, never per member; normally seconds. Retry after the next `FUNDING_SETTLED` event or once `GET /v1/funding/current` `nextFundingTs` lies in the future (section 7, "Catch-up fence") | 400 |
| 4001 | Eligibility version conflict | 409 |
| 5000 | Internal error: an unhandled server-side error; retry a read, reconcile order entry by `clOrdId` before resubmitting. Since revision 3.74 also a batch row whose outcome the venue could not confirm (section 6c): read the order back on the leader before sending it again. On a clustered deployment also a write whose commit the venue did not confirm within 10 seconds: its outcome is unknown and it may still execute (REST HTTP 500, and since revision 3.83 the WebSocket ack, which read `2013` before; section 10.7) | 500 (on a batch entry: a row inside a 200; on the WebSocket the ack) |
| 5001 | Engine busy, retry with backoff. On a clustered deployment 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: read it back by `clOrdId` on the leader before resubmitting (section 10.7) | 503 |
| 5002 | Venue halted | 400 |
| 5003 | Invalid request parameter (message names the parameter) | 400 |
| 5004 | Event store not configured on this deployment | 503 |
| 5005 | The venue could not complete this command; do not resubmit it unchanged | 500 (on order entry: `rejectCode` inside a 200) |
| 5006 | Ledger unavailable (section 6i): the chain host runs without its engine bridge, a venue-side fault; nothing happened, contact venue operations. A venue that does not run the Kronos X chain does not serve the chain routes at all (HTTP 404) | 503 |
| 5007 | History store busy: the read did not complete in time, or the store is busy; retry after data.retryAfterMs (revision 3.73) | 503 |
| 5030 | This node is not the leader; the body carries `leaderId` and `leader`, the environment's published entry host (clustered deployments only, among them the sandbox at `sandbox.mtf.perpetuals.net` from the date venue operations announce, section 1a; section 10.7). A redirect, never a rejection: the command did not reach the venue's sequence | 503 |

## 10. WebSocket API

### 10.1 Connection & session

- Endpoints: `wss://…/ws/public`, `wss://…/ws/private`. Text frames, JSON.
- Requests: `{"op": "...", "reqId": "...", "args": [...]}`. Every op is acknowledged:
  `{"op":"...", "reqId":"...", "success":true|false, "code":..., "msg":"...", "data":...}`.
- Liveness: send at least one frame every 60 seconds; `{"op":"ping"}` counts, and so
  does any other op. We recommend pinging every 30 seconds. A connection that sends
  nothing for 60 seconds is closed with code 4009 (`IDLE_TIMEOUT`). The server also
  emits a `{"channel":"heartbeat","type":"heartbeat","ts":...}` frame after 15
  seconds of outbound silence, so a healthy but quiet connection still shows traffic.
- Forward compatibility: ignore any frame whose `channel` you do not recognise. New
  channels and new control frames are added without a breaking-change notice.
- On reconnect, resubscribe and resync, every message carries `asOfSeq` /
  channel-`seq` so gaps are detectable. See section 10.8 for the full recovery
  algorithm. A venue restart closes every socket with status `1001`
  (`venue restart`) after an advisory frame; subscriptions are not preserved
  (section 10.7, "Restarts and reconnection").

**Private authentication** (revision 3.85: a server challenge, mandatory). The
FIRST frame the venue sends on every `/ws/private` connection, before anything
else, is its login challenge:

```json
{"channel": "challenge", "type": "challenge", "nonce": "5f2b8c0e9a4d4e7c8b1f3a6d2c9e0b7a", "ts": 1700000000000}
```

`nonce` is 32 lowercase hexadecimal characters (128 random bits) drawn for THIS
connection; `ts` is the venue clock in Unix ms. Read it, then send the `auth` op
with the nonce as the FOURTH arg:

```json
{"op": "auth", "reqId": "1", "args": ["<apiKey>", <expiresMs>, "<signature>", "<nonce>"]}
```

`signature = hex(HMAC_SHA256(secret, "GET/ws/private" + expiresMs + nonce))`, lowercase
hex, over the UTF-8 bytes of the three parts concatenated without separators:
`expiresMs` written as a plain decimal integer and `nonce` exactly as the challenge
carried it. Worked example: `secret=my-api-secret`, `expiresMs=1700000060000`,
`nonce=5f2b8c0e9a4d4e7c8b1f3a6d2c9e0b7a` → `d9e79457cc986b400928993ae3e89cd4e3a0f5fdeeb035d4a3768430fcbf0980`.

The signature is bound to the connection: an `auth` frame verifies on the
connection whose challenge it signs and on no other, so a frame captured on one
connection cannot open another (before revision 3.85 the recipe signed
`"GET/ws/private" + expiresMs` alone, and a captured frame authenticated a new
connection with full trading authority until that socket closed). The nonce stays
valid for the life of its connection: a second `auth` on the same connection (with
another key after a rotation, or after a revocation dropped the session back to
unauthenticated, section 10.6) signs the same nonce with a fresh `expiresMs`. A
reconnect is a new connection with a new challenge: never reuse a nonce. The public
endpoint `/ws/public` has no login and sends no challenge.

Refused `auth` frames answer `success: false` with code `1001`, the connection stays
open and keeps whatever state it had:

| Cause | `msg` |
|---|---|
| no fourth arg, or one that is not a non-empty string (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)` |
| a nonce that is not this connection's (a frame from another connection, or an old one) | `auth failed: the nonce is not this connection's challenge` |
| `expiresMs` more than 60 seconds after the venue clock | `auth failed: expiresMs too far in the future` |
| an unknown key, an `expiresMs` in the past, or a signature that does not verify | `auth failed` |

`expiresMs` must lie between now and now plus 60 seconds by the venue clock,
the same freshness bound as the REST recv-window cap. Sign a short-lived value
at connect time rather than a far-future constant.

Any active API key of your member authenticates a private session (multi-key,
section 3). The credential is re-validated on every private message, so a key
revoked mid-session stops working immediately (section 10.6); re-`auth` with
another active key to continue on the same socket.

**Migration table, revision 3.85** (dated 20 October 2026; the challenge is
mandatory from this release by the operator's decision of 2026-10-20, the venue being
before production go-live, the 30-day notice of the versioning policy waived for it;
change your login before you connect to a venue that runs it):

| What you do | Before revision 3.85 | From revision 3.85 | What to do |
|---|---|---|---|
| connect to `/ws/private` | the venue sent nothing until your first op | the first frame is `{"channel":"challenge","type":"challenge","nonce":...,"ts":...}` | read it before you send `auth` (a reader that ignores unknown channels must still keep its `nonce`) |
| send `auth` with three args, the signature over `"GET/ws/private" + expiresMs` | authenticated | refused `1001`, `auth failed: sign the nonce of this connection's challenge frame ...` | send four args: sign `"GET/ws/private" + expiresMs + nonce` and send the nonce as the fourth arg |
| send an `auth` frame on a connection other than the one whose challenge it signs (a frame kept from an earlier connection) | authenticated if its `expiresMs` was still ahead | refused `1001`, `auth failed: the nonce is not this connection's challenge` | sign every login on its own connection; after a reconnect sign the new challenge |
| log in again on the same connection | signed `"GET/ws/private" + expiresMs` | signs the same connection's nonce | keep the nonce for the life of the connection |

### 10.2 Public channels (`/ws/public`)

Subscribe: `{"op":"subscribe","reqId":"2","args":["orderbook.25.BTC-USD-PERP","trades.BTC-USD-PERP"]}`

| Channel | Content |
|---|---|
| `orderbook.{depth}.{symbol}` | L2 snapshot on subscribe, then deltas (depth 1/5/25/100) |
| `trades.{symbol}` | Public trades (price, qty, taker side, NORMAL/LIQUIDATION/ADL/RFQ/BARRIER_CLOSE/SWAP/REVERSAL flag; on a swap market `price` is the rate in basis points) |
| `ticker.{symbol}` | Best bid/ask, last, mark, index, funding, open interest |
| `markIndex.{symbol}` | Mark + index on every price tick |
| `funding.{symbol}` | Rate updates (`type: "rate"`), settlement results (`type: "settlement"`) and, on LOCAL_DAILY markets, the methodology figures behind each settlement (`type: "settlementDetail"`: `settlementValue`, `indexAtSnapshot`, `snapshotsUsed`, `snapshotTs`, `settleTs`; section 7) and, since revision 3.70, a day that closed without a settlement (`type: "skipped"`: `skipReason`, `settleTs`, `snapshotTs`, `settleCcy`, `ts`; best-effort, no funding exchanged, re-read the durable record with `GET /v1/funding/history?includeSkips=true`) |
| `status.{symbol}`, `status.venue` | Status transitions; `status.{symbol}` also repeats the market's unchanged status whenever the venue edits the market's configuration, so treat a frame as a prompt to re-read `GET /v1/status` or `GET /v1/instruments`, never as the source of a value (best-effort, section 10.7; revision 3.83 states it). On a SECURITY market with a call auction schedule (section 6j) the same channel carries each phase change as `{ tradingPhase, nextPhaseTs }` (`nextPhaseTs` Unix ms of the next scheduled transition) and each uncross as `{ auction: { price, qty, surplusQty, surplusSide } }` (`price` absent, the venue omits null fields, and `qty` `0` when nothing could trade) |
| `venueEvents` | Typed venue events (section 10.9). On a public socket: `SNAPSHOT`, `STOP_TRADING`, `RESUME_TRADING`, plus the advisory types `STALE_MARK_WARNING` and `STALE_MARK_RECOVERED` |

**Book depth tokens are a closed set.** The `{depth}` token in
`orderbook.{depth}.{symbol}` and `matchableBook.{depth}.{symbol}` must be one
of `1`, `5`, `25`, `100`, on `subscribe` and on `resync` alike. Any other
token, `orderbook.7.BTC-USD-PERP` for example, is refused with code `2013`
("unknown channel ..."), never clamped or served at a default depth. The
`depth` PARAMETER on `GET /v1/orderbook` and the `get.orderbook` /
`get.matchableBook` ops keeps its existing nearest-bucket behavior, unchanged.

**Per-session subscription ceiling.** One WebSocket session may hold at most
64 channels. A `subscribe` that would pass the cap is refused with code `2013`
and the message "subscription limit reached (64 channels per session)";
re-subscribing a channel the session already holds still succeeds at the cap.
Spread very wide market-data consumption over several sockets.

### 10.3 Private channels (`/ws/private`, after auth)

| Channel | Content |
|---|---|
| `order` | Full order lifecycle (accepted, triggered, amended, filled, canceled with reason, including liquidation cancels). `OrderFilled` reports the fill that completes the order; a partial fill is reported on the `execution` channel only, and every frame before the terminal one reads `status` `NEW` (`PENDING_TRIGGER` before a conditional triggers), an `OrderAmended` of a part-filled order included: the filled quantity is `qty` minus `remainingQty`. `acceptedTs` on every frame is the time of the event the frame reports (section 6, "`acceptedTs` per surface") |
| `execution` | Your executions with fees, role, flags, `clOrdId`, `clientUid` and, since revision 3.70 on PERP and BARRIER fills once the venue LEI is configured, the venue-leg `uti` (section 6.4); your own side only |
| `position` | Member position updates |
| `clientPosition` | Per-`clientUid` sub-position updates (high volume for large brokers, subscribe deliberately) |
| `margin` | Margin state updates + margin-call notices |
| `wallet` | Ledger entries (deposits, fees, funding, settlements, recovery distributions). Each entry carries the resulting balance twice: `balanceAfter` and the legacy alias `balance`, always equal; `balanceAfter` matches the field name on `GET /v1/account/ledger` |
| `matchableBook.{depth}.{symbol}` | Your matchable book: snapshot + deltas, rows `[price, matchableQty, ownQty]` |
| `eligibility` | Your Market Segments version changes: `{version}` for the account configuration, `{version, symbol}` for a per-market layer (revision 3.63); re-read with `get.marketSegments` |
| `adl` | ADL indicator changes (`type` `indicator`, `{adlQuintile}`, state class) and ADL executions affecting you (`type` `execution`, event class): `{episodeId, tradeId, symbol, qty, price, qtyLots, priceTicks, clientUid, positionSide (omitted for NET), role, side, ts}` (revision 3.70 documents the field list and adds the last three). One execution frame per allocation on YOUR side of a tear-up: `role` `DEFAULTER` (you were unwound; one frame per end client the venue closed, your `clientUid`; delivered from the engine sequence venue operations announce) or `DELEVERAGED` (your profitable position was closed against a defaulter); `side` `BUY` \| `SELL` is YOUR own close direction; `price` is the capped bankruptcy price both legs settled at |
| `unwind` | Liquidation/unwind episode lifecycle |
| `rfq` | RFQ lifecycle on RFQs you requested or quoted (section 6b): `created`, `quoted` (an anonymous quote arrived on your RFQ), `myQuote` (your own quote confirmed), `quoteCanceled`, `accepted` (both parties, with your `role`), `canceled`, `expired`. Every frame carries `closeContractId` when the RFQ is a close request (revision 3.44; omitted otherwise); a close request's `created` frame is ALSO delivered to the contract's writer, with `role` `WRITER` and no requester reference (the requester's copy says `role` `REQUESTER`), and `created` frames carry `takeProfit` when the request has one and, on barrier markets (revision 3.45), `strictTakeProfit`, `premiumBps` and `premium` (indicative at the current mark; the closed contract's frozen premium on a close request). Since revision 3.57 the WRITER's copy of a close request's frames additionally carries `capacity` and `clientTag` (writer transparency, section 6b); the requester's copies never do. On a SWAP market (revision 3.51, section 6h) the same channel carries the OFFER lifecycle: `created`, `canceled`, `expired` to the writer (each with `rateBps`, `tenorDays`, `minQty`, `remainingQty`) and `accepted` to BOTH parties of a take (`role` `WRITER` \| `TAKER`, the taken `qty`, `remainingQty` after it, `contractId`, `tradeId`); swap frames never carry quotes |
| `barriers` | Barrier contract lifecycle, delivered to both parties: `opened`, `knockOut`, `takeProfit`, `expiry`, `liquidation`, `finalSettlement`, `earlyClose`. Every frame carries the contract's terms (`barrier`, `takeProfit` when set, `expiryTs` when dated) and, since revision 3.45, `strictTakeProfit`, `premium` (the holder's maximum loss, frozen at the open) and `premiumBps`; closes carry `settlePrice`, your role-signed `pnl` and `terminationReason` (`KNOCK_OUT` \| `TAKE_PROFIT` \| `EXPIRY` \| `TEAR_UP` \| `FINAL_SETTLEMENT` \| `EARLY_CLOSE`, the same vocabulary as `GET /v1/barriers`). Since revision 3.57 the frames delivered to you as the WRITER additionally carry `capacity` and `clientTag` of the holder side (writer transparency, section 6b); the holder's frames never do. Since revision 3.58 your writer frames also carry the contract's frozen `levelOrigin` and, on FOR_CLIENT contracts, `clientClass` |
| `swaps` | Yield contract lifecycle (revision 3.51, section 6h), delivered to both parties with your `role` (`WRITER` \| `TAKER`): `opened`, `matured`, `default`, `cured`, `finalSettlement`. Every frame carries `contractId`, `symbol`, `asset`, `qty`, `notional`, `rateBps`, `tenorDays`, `reward`, `maturityTs`, `status`, `rfqId`, `tradeId`, `clientUid`, `ts` and a role-signed `amount` (what your role paid by this event is negative, what it received positive); settlement frames add `paid` (cumulative), `outstanding` (the claim still owed) and `reason`. Guaranteed, event-class, backfillable by cursor (section 11) |
| `venueEvents` | Typed venue events (section 10.9): the public trading-state and advisory types PLUS your own `LIQUIDATION`, `ADL`, `MARGIN_CALL`, `LIQUIDATION_COMPLETE`, since revision 3.61 `MEMBER_PARAMS` (a change of your own parameters, section 7) and since revision 3.70 the private advisory `MARGIN_WATCH` (your margin utilisation crossing or clearing the venue's early-warning level), scoped to your member only |

**Cross-surface field names.** Two quantities are spelled differently on
different surfaces; do not treat them as different numbers. The WS `wallet`
field `balance` is the same quantity the REST ledger (`GET /v1/account/ledger`)
calls `balanceAfter`; wallet frames and stream backfill rows carry both
spellings with equal values. The WS `margin` channel's lowercase `mm` is the
same quantity `GET /v1/account` reports as uppercase `MM`. Both legacy
spellings are frozen for deployed clients. Since revision 3.70 the REST ledger
row also carries `ccy` after `kind`, the entry's currency exactly as the
`wallet` frame and the stream `WALLET_ENTRY` row name it (the settlement
currency on cash rows, the base asset or swap asset on asset rows; an empty
string on a row recorded before the venue kept the column means the
settlement currency), so a cash entry can be matched to its asset on every
surface.

Note: channel events and op acks are both delivered only after durable persistence;
their relative order within a burst is not guaranteed (replies are written ahead of
event traffic, and guaranteed events ahead of market data), correlate by
`orderId`/`reqId`. The one ordering guarantee that IS made: within a single channel,
frames arrive in ascending (`asOfSeq`, `eventIndex`) order.

### 10.4 Order book protocol

On subscribe you receive a snapshot:

```json
{"channel":"orderbook.25.BTC-USD-PERP","type":"snapshot","seq":12,"asOfSeq":9041,
 "bids":[["64999.5","12"],["64999","3"]],"asks":[["65000","7"],["65000.5","20"]],"checksum":2016409727}
```

Then deltas (`qty "0"` deletes a level):

```json
{"channel":"orderbook.25.BTC-USD-PERP","type":"delta","prevSeq":12,"seq":13,"asOfSeq":9044,
 "changes":{"bids":[["64999.5","9"]],"asks":[]},"checksum":1333591281}
```

- If `prevSeq` does not match your last `seq`, resync: `{"op":"resync","args":["orderbook.25.BTC-USD-PERP"]}`.
  `resync` applies the same admission as `subscribe`: only book channels with a
  published depth token (1/5/25/100) and a known symbol are served, and
  `matchableBook` needs a private authenticated socket; anything else is
  refused with `2013` rather than silently acked.
- **Checksum**: CRC32-C (Castagnoli) over the UTF-8 canonical string of the top 25
  levels:
  `bidPx1:bidQty1|bidPx2:bidQty2|...#askPx1:askQty1|...`
  (bids best-first, then `#`, then asks best-first; prices/qtys exactly as the feed
  strings). A SNAPSHOT checksum covers the levels the snapshot carried, capped at the top
  25 per side (so `min(depth, 25)`): at depth 1, 5 or 25 it is over exactly your rows, at
  depth 100 it is over the top 25 of the 100. A DELTA checksum is always over the top 25 of
  the full book, whatever depth you subscribed, so a depth 1 or 5 subscription cannot
  recompute it and should verify snapshots only; depth 25 or 100 verifies continuously (the
  window-repair rows below keep the top 25 whole). Verify after the snapshot and, at depth
  25 or 100, after every delta; on mismatch, resync.
- **Window repair rows**: when a delta deletes levels, it also carries the levels
  that slide into the standard window boundaries (depth 1, 5, 25 and 100) from
  below, so a depth-limited window never goes short after a delete. Apply repair
  rows exactly like any other row and trim your stored window to your subscribed
  depth; a row restating a level you already hold is a harmless update, and a row
  deeper than your window is dropped by the trim. With these rows a depth 25
  subscription verifies the checksum continuously, deletes included.

### 10.5 Order entry over WebSocket

Same bodies, validation, and idempotency as REST, lower latency:

```json
{"op":"order.place",  "reqId":"r1", "args":[{ ...same body as POST /v1/orders... }]}
{"op":"orders.batch", "reqId":"r5", "args":[{ ...same body as POST /v1/orders/batch... }]}
{"op":"order.cancel", "reqId":"r2", "args":[{"orderId": 123456}]}
{"op":"order.cancelAll","reqId":"r3","args":[{"symbol":"BTC-USD-PERP"}]}
{"op":"order.amend",  "reqId":"r4", "args":[{"orderId":123456,"price":"65100","qty":"5"}]}
{"op":"orders.batchAmend",  "reqId":"r6", "args":[{"amends":  [ ...entries of POST /v1/orders/batch/amend... ]}]}
{"op":"orders.batchCancel", "reqId":"r7", "args":[{"cancels": [ ...entries of POST /v1/orders/batch/cancel... ]}]}
```

The `order.place` ack carries the same order-state payload as the REST response, and
is sent only after durable persistence. The ack is the verdict on the request: an
engine rejection acks `success: false` with the reject code, the registry wording
in `msg` and the order-state payload (`status` `REJECTED`, `rejectCode`) in
`data`; a gateway rejection acks `success: false` with the code and
`data.clOrdId`. REST reports the same engine rejection inside HTTP 200 with
`code` 0 and FIX in an ExecutionReport; section 6, "Where a rejection lands, per
transport", states why the three differ and that the convention stays.

**The single amend and cancel acks carry no order (revision 3.83 states it;
unchanged since before revision 3.24).** A successful `order.amend` or
`order.cancel` acks `{"op","reqId","success":true,"code":0,"msg":"ok"}` with no
`data`; only the `order.place` ack and the batch acks carry orders. Read the
state the venue applied from the `order` channel (`OrderAmended`,
`OrderCanceled`: `price`, `qty`, `remainingQty`, `status`, `clOrdId`,
`clientUid`; guaranteed and recoverable by cursor), from `get.order` or
`GET /v1/orders/{orderId}`, from the answers of the REST `PUT` and `DELETE`
(section 6, "Manage orders"), or from the row of a one-entry `orders.batchAmend`
or `orders.batchCancel` (section 6c; adopting the batch routes needs
re-certification, Conformance Test Plan X-07 to X-11). The venue never applies
part of an amendment: `qty` is the new total, fills included, and the matching
engine applies the price and quantity you sent exactly or refuses the whole
amendment; only `remainingQty` can differ from what you computed, by the fills
sequenced before the amendment. A success ack means the amendment was taken,
not that the order still stands: a reprice that crosses re-matches the order,
which can trade, be rejected `2005` as post-only or meet your self-trade policy,
and the `order` and `execution` channels carry that outcome.

`orders.batch` mirrors `POST /v1/orders/batch` exactly (section 6c): 1 to 50
independent entries submitted in array order, `data.results` in array order on
the ack with the same per-entry shapes as REST, the batch charged N
order-budget units up front all or nothing, and every entry counted by the
order-to-trade regime. The ack rides the same guaranteed reply channel as
every other order ack.

`orders.batchAmend` and `orders.batchCancel` (revision 3.74) mirror
`POST /v1/orders/batch/amend` and `POST /v1/orders/batch/cancel` exactly
(section 6c): the same entries, the same per-entry rows in array order, the
same charge (one order-budget unit per amendment entry after the engine-busy
check; the cancellation batch free) and the same one cancellation batch of
yours at a time on a node. Their success ack is
`{"op", "reqId", "success": true, "code": 0, "msg": "ok", "data": {"results": [...]}, "asOfSeq", "ts"}`,
with `asOfSeq` at the top level as the read acks carry it (section 10.6), the
evidence the Conformance Test Plan asks you to keep; `orders.batch` keeps its
ack without `asOfSeq`. An envelope refusal acks `success: false` with the code:
`1003` with `data.retryAfterMs`, `1005` after the live re-read of your
credential, `2002`, `5001` and, on a clustered deployment's follower node,
`5030` with `msg` `NOT_LEADER` and `data` `{leaderId, leader}` before anything
is charged or counted. A withdrawn route acks `2013` "unknown op", exactly what
a gateway before revision 3.74 acks. The WebSocket single `order.amend` and
`order.cancel` acks carry no `rejectCode`: an amendment or cancel that took
effect before a contained fault acks as taking effect, and an amendment the
re-match rejected (a post-only order repriced to cross, `2005`) acks accepted
while the `order` channel carries the rejection.

**`order.cancelAll` reads `symbol` and `side`, and nothing else (revision 3.84).**
Its args are exactly one object whose only members, if any, are `symbol` and
`side`, each a string sent at most once. `side` reads exactly as on
`DELETE /v1/orders?side=`: `BUY` or `SELL` in any case cancels that side only
(with `symbol`, that side on that market), and any other string, an empty one
included, answers `5003` "side must be BUY or SELL; nothing was cancelled" (the
operator's decision of 2026-10-21). Without a filter (`[{}]`) the op cancels
every open order you have on every market, untriggered conditionals included.
Anything else is refused and nothing is cancelled: a member the op does not
define answers `2017` naming it (member names are case-sensitive; a batch body
sent here by mistake, `{"cancels": [...]}` meant for `orders.batchCancel`, is
one), a `null` or a value that is not a string, a member given twice, `args`
given twice in the message and args that are not exactly one object (none, `null`, an object, an empty array, two
objects) `2013`. Before revision 3.84 the op read `symbol` alone: every other
member, `side` included, and every args object after the first were ignored,
of two `args` keys the last one was read, and a `null` `symbol` read as absent, so each of these cancelled every open
order the rest of the args matched, on both sides (the migration table of
section 6, "Manage orders").

### 10.5a Cancel on disconnect (`session.cancelOnDisconnect`)

An opt-in protection: arm it on a private session and, if that session drops
and you do not come back within a grace window, the venue cancels your open
orders for you.

```json
{"op":"session.cancelOnDisconnect","reqId":"c1","args":[{"enabled":true,"graceMs":5000}]}
```

- Private socket only, after `auth`. The credential must be able to trade,
  re-validated live exactly like the order ops: a READ_ONLY (drop copy)
  credential is refused with code `1005`, because arming causes cancels.
- `graceMs` is clamped to 0 to 30000; absent means 0 (cancel immediately on
  disconnect). The success ack `msg` reports the armed state:
  `armed graceMs=5000`, or `disarmed` when `enabled` is false.
- **Armed per SESSION, in memory only.** The arming is not persisted and does
  not survive the socket: a reconnect starts unarmed and must re-arm. A member
  that wants the protection arms every session it opens.
- **What firing does.** When an armed session's socket closes, for any reason,
  the venue waits the grace window and then, if the member has NO live
  authenticated TRADING-CAPABLE private WebSocket session left, submits ONE
  member-scope cancel-all: every open order of the member, on every instrument,
  untriggered conditionals included, regardless of the transport that placed
  them. A session on a READ_ONLY key does not count as the member being back:
  a credential that cannot cancel or manage orders can neither suppress nor
  disarm the protection, on WebSocket and FIX alike. The venue re-checks
  liveness immediately before submitting, so a reconnect that lands even at
  the last instant still stops the cancel. The
  canceled orders report `cancelReason` `USER_REQUESTED`, because the armed
  protection is your own standing instruction; the cancels reach you on the
  `order` channel, on FIX drop copy and in order history like any cancel-all
  you send yourself. By its fields such a cancel cannot be told apart from a
  member-wide cancel-all of your own (`order.cancelAll` or `DELETE /v1/orders`
  without a symbol, from any session of yours) or from the member portal's kill
  switch, which report `USER_REQUESTED` too; `ADMIN_CANCEL` is reserved for
  cancels by venue operations, and no `ExecRestatementReason(378)` is set on
  FIX. What tells the protection apart: it fires only after an armed session
  closed and the grace window passed with no trading-capable session of yours
  left on that transport; it is ONE command, so all its `OrderCanceled` frames
  share one `asOfSeq` (on FIX one engine sequence prefix in `ExecID(17)`); it
  covers all your open orders on every market, whichever transport placed them;
  and it answers no request of yours (revision 3.83 states it; the value stays
  `USER_REQUESTED` by design).
- **Member scope is deliberate.** Orders belong to members, not to
  connections (every session is a full-member drop copy, one margin account),
  so there is no venue-side notion of "the orders this socket placed" and no
  partial per-connection cancel is offered.
- **A reconnect disarms it.** A connection that AUTHENTICATES on a private
  WebSocket with a trading-capable credential within the grace window clears
  the pending timer; nothing is canceled. A read-only auth clears nothing.
  Timers are per transport: a WebSocket timer is cleared only by a WebSocket
  `auth`, a FIX timer only by a FIX TRADING Logon (a drop-copy Logon clears
  nothing), so a WS reconnect does not defuse a pending FIX cancel or vice
  versa. One timer is pending per member per transport. A second armed session
  of the same member dropping restarts the window on a BEST EFFORT basis: the
  restart can race a timer already at its expiry, so treat the restarted
  window as advisory, not as a guaranteed extension; the venue re-checks
  liveness immediately before submitting either way.
- **Arming survives a key rotation.** The armed protection is keyed to your
  MEMBER identity captured when the session armed it, so rotating or re-homing
  the session's API key mid-session does not silently void an acknowledged
  arming: if the session later drops, the grace timer still starts.
- **A venue restart voids arming.** Arming is in-memory by design, so a node
  restart or failover voids every pending timer and every armed session
  uniformly: nothing fires during or after the restart, and every reconnect
  starts unarmed. If you rely on the protection, re-arm on every connect and
  treat a disconnect you observe around a venue restart as unprotected.
- **Orders only, never positions.** The venue does not close, reduce or hedge
  any position on a disconnect, under this feature or any other. If you need
  your book flat after a disconnect, that remains your own reconnect logic.
- On FIX the same protection is armed at Logon via venue tags `9004`/`9005`;
  see the FIX rules of engagement.

### 10.6 Synchronous requests over WebSocket (REST parity)

Every live-state REST read is also answerable synchronously on the socket with
`get.*` ops. The response `data` has the same shape as the corresponding REST
`data` payload and carries `asOfSeq` exactly like the REST envelope, so you can
run entirely on one WebSocket connection if you prefer. Correlate via your
`reqId`:

```json
{"op":"get.<resource>", "reqId":"q1", "args":[{ ...params... }]}
```

| Op | REST equivalent | Params | Auth |
|---|---|---|---|
| `get.time` | `GET /v1/time` | none | no |
| `get.instruments` | `GET /v1/instruments` | none | no |
| `get.status` | `GET /v1/status` | none | no |
| `get.orderbook` | `GET /v1/orderbook` | `symbol`, `depth` | no |
| `get.ticker` | `GET /v1/ticker` | `symbol` | no |
| `get.trades` | `GET /v1/trades` | `symbol`, `limit` | no |
| `get.funding` | `GET /v1/funding/current` | `symbol` | no |
| `get.indexPrice` | `GET /v1/index-price` | `symbol` | no |
| `get.markPrice` | `GET /v1/mark-price` | `symbol` | no |
| `get.insuranceFund` | `GET /v1/insurance-fund` | none | no |
| `get.account` | `GET /v1/account` | none | yes |
| `get.positions` | `GET /v1/positions` | `symbol?` | yes |
| `get.clientPositions` | `GET /v1/positions/end-clients` | `symbol?`, `clientUid?` | yes |
| `get.openOrders` | `GET /v1/orders/open` | `symbol?` | yes |
| `get.order` | `GET /v1/orders/{id}` | `orderId` | yes |
| `get.executions` | `GET /v1/executions` | `symbol?`, `limit` | yes |
| `get.adlIndicator` | `GET /v1/adl/indicator` | `symbol?` | yes |
| `get.unwinds` | `GET /v1/unwinds` | none | yes |
| `get.marketSegments` | `GET /v1/market-segments` | `symbol?` (revision 3.63: the per-market layer) | yes |
| `marketSegments.set` | `PUT /v1/market-segments` | `restricted`, `allow`, `version`, `symbol?` (revision 3.63: the per-market layer; the ack carries `symbol`) | yes |
| `get.matchableMembers` | `GET /v1/market-segments/matchable-members` | `symbol?` (revision 3.63: on that market) | yes |
| `get.matchableBook` | `GET /v1/orderbook/matchable` | `symbol`, `depth` | yes |
| `get.marginPreview` | `POST /v1/margin/preview` | `orders: [...]` | yes |
| `get.streamCursor` | `GET /v1/stream/cursor` | none | yes |

Example: an on-request snapshot of your end-client sub-positions (the same data
the `clientPosition` channel streams incrementally):

```json
{"op":"get.clientPositions", "reqId":"q2", "args":[{"symbol":"BTC-USD-PERP", "clientUid":"CLIENT-A"}]}
```

Public `get.*` ops work on both `/ws/public` and `/ws/private`; private ops
require an authenticated `/ws/private` session. Private `get.*` requests and
order ops alike are re-checked against your API key on every message: a revoked
key stops working immediately with code 1001, and the session drops back to
unauthenticated without the socket closing. Your member's other keys are
unaffected: re-`auth` with another active credential (section 3), signing the
same connection's challenge nonce (section 10.1), and resubscribe. `get.*` ops consume the same read rate-limit budget as their REST
twins. Since revision 3.73 `get.streamCursor` also acks `5007` or `1003` with
`data.retryAfterMs` (section 4): `5007` when the venue's history store did not
answer in time, is held after a slow read, or is catching up; `1003` when more
reads of yours of the event stream group are in flight than the node serves at
once, or when the node's budget of these reads that every member shares is full.
Sleep the wait and send the op again.

**Not mirrored over WS**, REST-only by design: the historical and EOD
reconciliation endpoints and the event-stream backfill read
(`GET /v1/stream/events`), which serve large range-scoped datasets. The cheap
cursor read IS available on both surfaces (`get.streamCursor` /
`GET /v1/stream/cursor`). `get.executions` returns the recent-executions ring
(the same rows as the ring-backed REST route); for date-ranged execution history
use REST.

**Book seeding note.** `get.orderbook` is a one-shot read and, like the REST
route, carries no per-channel `seq`. To drive an incrementally maintained book,
seed it from the `subscribe`/`resync` snapshot (which carries `seq`) rather than
from `get.orderbook`.

### 10.7 Delivery guarantees

Every channel belongs to exactly one of two delivery classes:

| Class | Channels | Overflow behaviour |
|---|---|---|
| `GUARANTEED` | `order`, `execution`, `wallet`, `eligibility`, `adl` (type `execution`), `unwind`, `margin` (type `marginCall`), `venueEvents`, `rfq`, `barriers`, plus every op ack and `get.*` reply | never silently dropped; if you cannot keep up, a terminal `gap` frame is sent and the connection is closed with code 4008 |
| `BEST_EFFORT` | `orderbook.*`, `matchableBook.*`, `trades.*`, `ticker.*`, `markIndex.*`, `funding.*`, `status.*`, `position`, `clientPosition`, `margin` (type `update`), `adl` (type `indicator`) | may be dropped under pressure; drops are announced with a non-terminal `gap` frame and the stream continues |

Any channel added in the future is `BEST_EFFORT` unless documented otherwise.
`funding` settlement results are best-effort deliberately: the cash effect of a
settlement is a ledger entry that arrives on the guaranteed `wallet` channel, and the
rate itself is reproducible from `GET /v1/funding/history`.

**Close codes** (the only Kronos X specific codes; everything else is standard
RFC 6455):

| Code | Reason string | Meaning |
|---|---|---|
| 1001 | `venue restart` | normally the venue is stopping (rollout or restart); the same close is also sent, while the venue keeps running, to every session that may have missed a frame when a venue-side fault lost frames of one command or batch; in both cases reconnect with backoff, resubscribe, backfill the guaranteed families from your last cursor and re-read state for the others (see "Restarts and reconnection" below) |
| 1001 | `leader change, reconnect` | clustered deployments only (revision 3.76): the node you were connected to no longer leads; the venue stays up, reconnect and resubscribe (see "Leader change" below) |
| 4008 | `SLOW_CONSUMER` | guaranteed queue or reply queue overflow: you stopped reading |
| 4009 | `IDLE_TIMEOUT` | no inbound frame for 60 seconds |

A `/ws/public` connection is never closed because of market-data backpressure: book,
ticker, trade and status conflation is signalled with a non-terminal gap frame and
the socket stays open. A connection of either kind can still be closed with 4008 if
it stops reading the replies to its own requests, and with 4009 if it stops sending.

**Restarts and reconnection.** When the venue stops for a rollout or a restart it
closes every WebSocket deliberately instead of letting the connection die with the
process. On each open socket, public and private alike and whatever it is
subscribed to, the venue first sends one advisory control frame

```json
{"channel":"advisory","type":"advisory","advisory":"restart",
 "msg":"The venue is restarting; reconnect and resubscribe.","ts":1785312044518}
```

and then the close frame with status `1001` (RFC 6455 "going away") and reason
string `venue restart`. Guaranteed frames already released to your socket are
written before the advisory; best-effort frames may be dropped. The whole
broadcast-then-close runs inside a bounded window of about two seconds, after which
a socket whose peer has not answered the close handshake is aborted. While the
venue is stopping, a new WebSocket upgrade is refused with HTTP `503` (with a
`Retry-After` header) rather than accepted and closed a moment later.
The venue sends exactly the same advisory and close in one other case, while it
keeps running: when a venue-side software fault lost WebSocket frames or FIX
ExecutionReports of one command or batch (the command itself executed and is
durable), it closes every session that may have missed one of them, and
cancel-on-disconnect arming is voided as on a restart. The venue keeps running,
so new upgrades are not refused. The cursor backfill below delivers every missed
frame of the guaranteed families; for the best-effort families and for `rfq`,
`barriers` and `venueEvents` you re-read state (section 10.8), exactly as after a
restart. The venue adds no other close code or reason for this case. What to do:

- Subscriptions and `auth` state are NOT preserved across a restart: after any
  close, and after 1001 in particular, reconnect with exponential backoff starting
  at 1000 ms, `auth` again on a private socket, and resubscribe every channel
  (`session.cancelOnDisconnect` must be re-armed too, section 10.5a; a restart voids
  all in-memory arming and nothing fires).
- Recover exactly as after any other disconnect (section 10.8): resume the
  guaranteed families from your last cursor over `GET /v1/stream/events`, and
  re-read state for the best-effort families and for `rfq`, `barriers` and
  `venueEvents` (fresh `SNAPSHOT` on resubscribe). Drop-copy and other private
  channels behave the same as order-entry sessions.
- Requests and the trip: an op the venue had already read when the restart
  began completes and its ack is written before the close (executed means
  acked); anything you send after the advisory (or that arrives after the venue
  tripped your socket) is NOT processed and gets no ack: re-send it after the
  reconnect. Reconcile from the backfill as always; you will never find an
  executed command whose ack you did not receive. A leader change is the one
  exception: there the ack of a write the venue had already read can be `5001`
  or `5000` although the write may still execute ("The other answers during a
  leader change" below).
- Treat the advisory as informational; a client that only handles the close code
  behaves correctly. Ignore the `advisory` channel if you do not recognise it, like
  any other unknown channel.
- FIX sessions receive `Logout(35=5)` with `Text(58)="venue restart"` before the
  TCP close (FIX guide, section 1).

**Leader change (clustered deployments, revision 3.76).** On a clustered
deployment one node, the leader, accepts writes, and the venue's entry address
always leads to it. When leadership moves to another node while the former
leader stays up (a planned hand-over during a rollout, or a node cut off from
the others), that node closes every WebSocket the way a restart does: the
replies and guaranteed frames already released, then the advisory

```json
{"channel":"advisory","type":"advisory","advisory":"leaderChange",
 "msg":"The venue's leader node changed; reconnect and resubscribe.","ts":1791791993568}
```

and the close frame with status `1001` and reason string
`leader change, reconnect`; FIX sessions receive `Logout(35=5)` with
`Text(58)="leader change, reconnect"`. Do what you do after a restart:
reconnect (the entry address now leads to the new leader), `auth` again,
resubscribe, re-arm `session.cancelOnDisconnect` (a leader change voids arming
like a restart, and nothing fires) and recover from your cursor (section 10.8).
New upgrades are not refused. A write op that reaches a node which is not the
leader (`order.place`, `order.cancel`, `order.cancelAll`, `order.amend`,
`orders.batch`, `orders.batchAmend`, `orders.batchCancel`, `marketSegments.set`,
`session.cancelOnDisconnect`) acks `success` false, `code` `5030`, `msg`
`NOT_LEADER`, with `leaderId` and `leader` under `data`, first: before
authentication, validation, or anything charged or counted; before revision
3.76 the single ops answered `5001` "busy" there, and
`session.cancelOnDisconnect` acked `armed` although a node that is not the
leader cannot submit the cancel. A single-node deployment never sends
either; the clustered sandbox at `sandbox.mtf.perpetuals.net` (section 1a,
from the date venue operations announce) sends both at a leader change. Since revision 3.77 a FIX session gets the leader-change Logout from
the node itself as well: an order, cancel, replace or market data request that
reaches a node which is not the leader is answered with `Logout(35=5)`,
`Text(58)="leader change, reconnect"` and the close (reconcile by `ClOrdID(11)`
on the leader before resubmitting), and that close arms no cancel-on-disconnect (FIX guide, section 1).

**The leader hint (revision 3.83 states it; nothing changed).** `leader` is the
member-facing base URL venue operations configure for the node that leads: an
`https` base URL, never a WebSocket URL, and never derived from a node's own
host name. On every venue deployment it is the environment's published entry
host of section 1, which sends new connections only to the leader. Accept the
hint only when it equals the entry host you configured for the environment,
and then open a NEW connection to that host (`wss://<host>/ws/private` for the
private socket, section 10.1). If it ever names another host, send no
credentials there: reconnect to your configured entry host and tell venue
operations, because it would be a configuration error on the venue's side.
While no leader is known (during an election) REST answers `leaderId` 0 with
`leader` null and the WebSocket ack carries `leaderId` 0 and no `leader`: back
off briefly and retry your entry host.

**What a node that is not the leader does (revision 3.83 states it).** On REST
it answers every request that is not a `GET` under `/v1` with HTTP 503 and code
`5030` before the route runs, a read-only `POST` such as
`POST /v1/margin/preview` included, and never forwards it; over HTTP/2 it first
reads the rest of the request's body, for at most 2 seconds. It serves `GET`
reads, and it closes every HTTP connection after its answer, reads included
(`Connection: close` on HTTP/1.1, GOAWAY on HTTP/2), so your next request opens
a new connection, which the entry host sends to the leader. It does NOT close a
WebSocket opened on it: it serves `auth`, reads and streams there and acks
every write op `5030`, so at the first `5030` close the socket yourself,
reconnect to the entry host, `auth`, resubscribe, re-arm
`session.cancelOnDisconnect` and recover from your cursor (section 10.8). Only
a node that led closes its own sockets when it loses the role (the advisory and
the `1001` close above). A reconnect just after a leader change can still land
on the former leader and read `5030` again: retry after a short delay. Branch
on the code, never on HTTP 503 alone, which `5001`, `5004` and `5007` share.

**A `5030` is a redirect, never a rejection.** It is answered before the
command reaches the venue's sequence, and a node that is not the leader never
forwards a write, so a command answered `5030` cannot reach the book later.
Send it again on the leader with the SAME `clOrdId`, and never count it as a
reject or let it stop your trading. A cancel, cancel-all, amendment or
cancellation batch answered `5030` did not take effect: the order still rests
at its old terms, so send it again on the leader at once. A
`session.cancelOnDisconnect` answered `5030` is not armed. A leader change
itself pulls no resting order and voids every cancel-on-disconnect arming:
re-arm when you reconnect, then re-quote or cancel what you no longer want.

**The other answers during a leader change.** None of these proves that the
command was not executed:

- `5001` on a SINGLE write, REST or WebSocket, that was already in the former
  leader's log when it lost the role: it may still execute on the new leader at
  its original terms (a batch row's `5001` is never such a write);
- `5000` on a write whose commit the venue did not confirm within 10 seconds:
  HTTP 500 on REST and, since revision 3.83, the WebSocket ack, which read
  `2013` "malformed request" before;
- a batch row `5000` "outcome unknown" (section 6c);
- no ack at all for an op you sent after the `1001` close.

Read such an order back on the leader by `clOrdId` (`GET /v1/orders/open`,
`GET /v1/orders/history` or the order stream of section 11) before you decide,
or send it again with the SAME `clOrdId`; never send it again under a new
reference. On FIX the same holds for an order in flight at the leader-change
Logout, and for one in flight when the venue does not confirm a write's commit
within 10 seconds: the venue then closes the FIX session without a Logout and
without a report, and unlike the leader-change Logout that close counts as a
disconnect for an armed cancel on disconnect, which runs after its grace window
unless a FIX trading session of yours logs on first (FIX rules of engagement
section 1, revision 2.30 states it). Reconcile by `ClOrdID(11)` on the leader.

**Migration table, revision 3.83** (dated 20 October 2026; the WebSocket ack
of a write whose commit is not confirmed and the hold of `GET /v1/executions`
and `GET /v1/account/ledger` below a store hole are corrections in force at
publication by the operator's decision of 2026-10-20, the venue being before
production go-live, the 30-day notice of the versioning policy waived for
them, as for item (3) of revision 3.73, revision 3.81 and revision 3.82; this
table is the guide of what to do):

| What you do | Before revision 3.83 | From revision 3.83 | What to do |
|---|---|---|---|
| send a single WebSocket write op on a clustered deployment whose commit the venue does not confirm within 10 seconds | the ack `2013` "malformed request" | the ack `5000` `internal error`, the answer REST gives the same case (HTTP 500) | treat the outcome as unknown, never as a request to fix: read the order back on the leader by `clOrdId`, or send it again with the SAME `clOrdId` |
| read `GET /v1/executions` or `GET /v1/account/ledger` while `store.status` reads `REPAIRING` | the whole table: rows from after the hole could be served while the hole's own rows were missing, under an `asOfSeq` naming the watermark | only the rows at or below the watermark, answered as `asOfSeq`, with and without a `symbol` or `kind` filter | read the range again once the status is `COMPLETE`; never take a page read while `REPAIRING` as complete |

**Planned maintenance windows.** Trading is continuous, twenty-four hours a day
on every calendar day on every market other than a SECURITY market with an
auction schedule (section 6j), per market as stated in the contract
specifications and in the Trading hours paragraph of section 5. A release that requires the venue
to stop is announced on the Website and through the operational channels of
section 1a at least five Business Days ahead (date, start time in UTC, expected
duration), placed inside the standard maintenance window (Saturday 02:00 to
06:00 UTC) and outside funding and settlement times, and executed exactly with
the restart procedure above: resting orders, positions, standing offers and
open contracts survive it, subscriptions and cancel-on-disconnect arming do
not. The sandbox reset policy of section 1a is unchanged. A stop without
notice remains possible under the Rulebook's system disruption provision.

**Terminal gap frame**, sent immediately before close 4008:

```json
{"channel":"gap","type":"gap","reason":"SLOW_CONSUMER","delivery":"GUARANTEED",
 "terminal":true,"channels":["execution","margin","order","wallet"],
 "fromSeq":904118,"fromEventIndex":2,"inclusive":true,"toSeq":null,"dropped":1287,
 "backfill":{"path":"/v1/stream/events",
   "query":"fromSeq=904118&channels=execution,margin,order,wallet&limit=500"},
 "unrecoverable":null,"ts":1785312044518}
```

- `fromSeq`/`fromEventIndex`: the cursor of the last guaranteed frame actually
  written to your socket, INCLUSIVE (`"inclusive":true`). The backfill request
  resumes at `fromSeq` at seq granularity, so the first backfilled rows may be
  ones you already applied; dedupe (see 10.8).
- `channels`: affected guaranteed family names, sorted; may over-report, never
  under-reports.
- `dropped`: a lower bound on the number of frames you did not get. Advisory.
- `backfill`: a ready-made first backfill request, or `null` on a public or
  unauthenticated connection. It names only families with sequence-ordered history
  in the event store (section 11).
- `unrecoverable`: guaranteed families with NO sequence-ordered history
  (`venueEvents`, `rfq` and `barriers`, when subscribed); `null` when every
  affected family is backfillable. For `venueEvents`, resubscribe and apply the
  fresh `SNAPSHOT`, plus `GET /v1/status`. For `rfq` and `barriers`, recovery is
  state-only: re-read `GET /v1/rfq/mine` and `GET /v1/rfq/open` (plus
  `GET /v1/rfq/quotes/mine` if you quote), and `GET /v1/barriers`.

**Non-terminal gap frame**, delivered inline; the connection stays open:

```json
{"channel":"gap","type":"gap","reason":"CONFLATED","delivery":"BEST_EFFORT",
 "terminal":false,"channels":["orderbook","ticker","trades"],
 "fromSeq":903990,"toSeq":904087,"dropped":412,"backfill":null,"unrecoverable":null,
 "ts":1785312041205}
```

It means: at least one frame in these families was dropped; re-read the state you
care about (see the recovery table in 10.8). `fromSeq`/`toSeq` may both be `null`
when the exact bounds were not observable.

### 10.8 Recovering a gap

Reconnect algorithm:

1. While connected, track the highest (`asOfSeq`, `eventIndex`) seen per guaranteed
   channel and, per backfill filter, the `nextCursor` of your last page.
2. On a terminal `gap` frame, keep its `fromSeq`/`fromEventIndex`, note
   `unrecoverable`, and expect close 4008. The cursor is inclusive: the first
   backfilled row may be one you already applied.
3. On any close without a gap frame (network drop, 4009), use your resume point
   (section 11.6).
4. Reconnect with exponential backoff starting at 1000 ms. Do not reconnect tighter
   than that after a 4008: you were disconnected for not reading.
5. `auth`, then `subscribe`, and START BUFFERING live frames immediately; do not
   process them yet.
6. Page `GET /v1/stream/events?fromSeq=C&channels=...` (section 11), then repeat
   with `afterCursor=nextCursor` while `hasMore` is true, applying rows
   idempotently (dedupe key below). Stop when `hasMore` is false and
   `store.complete` is true, or when `nextCursor` passes the cursor of the first
   buffered live frame; a page with `hasMore` false or `count` 0 ends the paging
   loop, never `nextCursor` equal to the `afterCursor` you sent (since revision
   3.73 a last page answers a position at the store watermark where the venue
   serves it, section 11.3). The
   accepted `channels` values are exactly the ten families `order`, `execution`,
   `wallet`, `margin`, `adl`, `unwind`, `eligibility`, `funding`, `status`,
   `swaps`; naming any other family is rejected with code 2019, and a value that
   names no family (empty, commas only) with code 5003 (revision 3.73).
7. For every family named in `unrecoverable`, and for the best-effort state you care
   about, re-read state once (table below).
8. Drain the buffer, discarding every frame whose dedupe key you already applied,
   then resume live consumption. If `store.status` is `CATCHING_UP`, keep polling
   from `nextCursor`; live frames beyond the store watermark reach you on the
   socket anyway and the cursor comparison keeps them in order. If it is
   `REPAIRING`, keep polling from `nextCursor` (it stops below the hole, never
   past it) and raise an operational alert: reads are clamped below the
   venue-side hole, so you will stall rather than skip, and advance again once
   the venue repairs. Until then you cannot backfill past the hole: recover the
   state you need with the reads of step 7, and keep consuming live frames.
   Trading, the live WebSocket and FIX feeds and the open-order and position
   reads do not depend on the store. The venue sets no maximum duration for
   `REPAIRING`, and it is expected at every leader change of a clustered venue,
   so the first backfill after such a reconnect can read it (section 11.3).

**Dedupe key (required from day one).** (`asOfSeq`, `eventIndex`) is NOT a unique
frame identity: a member that is both maker and taker on one trade receives two
`execution` frames on the same cursor. Dedupe on the cursor PLUS the natural key:
`tradeId`+`role` for `execution`, `orderId`+`type` for `order`, `entryId` for
`wallet`, `version` for `eligibility`, `tradeId` for `adl`. For `venueEvents`,
`eventId` alone is the dedupe key (except `SNAPSHOT`, see section 10.9).

**Best-effort recovery table** (state re-read; no cursor needed):

| Family | Re-read |
|---|---|
| `trades` | `GET /v1/trades?symbol=...&limit=1000` |
| `funding` | `GET /v1/funding/history?symbol=...&from=...&to=...` (add `includeSkips=true` for the LOCAL_DAILY days that closed without a settlement, revision 3.70) |
| `ticker` | `GET /v1/ticker?symbol=...` |
| `markIndex` | `GET /v1/mark-price`, `GET /v1/index-price` |
| `status` | `GET /v1/status` |
| `position` | `GET /v1/positions` |
| `clientPosition` | `GET /v1/positions/end-clients` |
| `margin` | `GET /v1/account` |
| `adl` (indicator) | `GET /v1/adl/indicator` |
| `orderbook`, `matchableBook` | `{"op":"resync","args":["orderbook.25.BTC-USD-PERP"]}` |

### 10.9 Venue events (`venueEvents`)

One channel carrying typed, machine-readable venue events: trading stops and
resumptions with a reason, advisory early warnings for the mark-price staleness
ladder and (privately, revision 3.70) for your own margin utilisation, and your
own liquidation, auto-deleveraging and margin-call activity.
Subscribe exactly as any other channel:

```json
{"op":"subscribe","reqId":"7","args":["venueEvents"]}
```

Available on BOTH sockets:

- `/ws/public`: delivers `SNAPSHOT`, `STOP_TRADING`, `RESUME_TRADING` and the two
  advisory types `STALE_MARK_WARNING` / `STALE_MARK_RECOVERED`. The trading-state
  and advisory types are market-wide venue facts with no member content and belong
  on the public feed for pre-trade transparency.
- `/ws/private` (after `auth`): those five PLUS `LIQUIDATION`, `ADL`,
  `MARGIN_CALL`, `LIQUIDATION_COMPLETE`, `MEMBER_PARAMS` (revision 3.61) and the
  private advisory `MARGIN_WATCH` (revision 3.70), scoped to your member only. These
  carry end-client identifiers or your own account figures and are never delivered
  to another member or to a public session. A `/ws/private` session may subscribe BEFORE `auth` (it is not an error)
  and then behaves exactly like a public session until `auth` succeeds.

If you want everything on one subscription, subscribe on `/ws/private` only. A
client subscribed on both sockets receives the trading-state and advisory types
twice with the SAME `eventId`, so duplicates are dropped by identity.

**Envelope** (every frame):

```json
{"channel":"venueEvents","type":"STOP_TRADING","eventId":"904118-2","asOfSeq":904118,
 "ts":1785000123456,"data":{}}
```

- `type`: SCREAMING_SNAKE, one of the eleven values below.
- `eventId` (string): `"{asOfSeq}-{eventIndex}"`. Globally unique, stable across
  reconnects and across the public/private sockets: your idempotency key for every
  type EXCEPT `SNAPSHOT`. For `SNAPSHOT` the value is `"snapshot-{asOfSeq}"` and is
  NOT an idempotency key: two subscribes at the same `asOfSeq` (for example your
  public and private sockets) produce the same string on two genuinely distinct
  snapshots. Apply every `SNAPSHOT` you receive on the connection that received it;
  never dedupe snapshots by `eventId`. The advisory types (`STALE_MARK_WARNING`,
  `STALE_MARK_RECOVERED` and, since revision 3.70, `MARGIN_WATCH`) use the namespace
  `"advisory-{asOfSeq}-{n}"`, `n` one counter shared by every advisory the venue
  process emits: unique, collision-free with the other namespaces, and a valid
  dedupe key across your two sockets.
- `asOfSeq` (int64): engine sequence number, same semantics as every other channel.
  For the advisory types it is the engine sequence observed at emission.
- `ts` (int64): milliseconds since Unix epoch (engine command time; for `SNAPSHOT`
  and the advisory types, server wall clock at emission).
- `data`: type-specific, flat, documented below.

**Serialization note.** Unlike the rest of the API, which omits null members, this
channel WRITES nulls: an explicit `"clOrdId": null` means "venue-initiated, there is
no MEMBER-authored reference", and a present null is unambiguous where an absent key
is not. On the `execution` channel the same venue-initiated closes carry the VENUE
reference instead (`"clOrdId": "KX-LQ-{orderId}"` or `"KX-ADL-{tradeId}"` with
`"refSource": "VENUE"`, section 6.4): key on `refSource`, never on the shape of
`clOrdId`. Historical rows recorded before the venue-reference scheme may still
show an empty `clOrdId` on venue-initiated legs; treat empty, null and `KX-` values
uniformly as "not a member reference".

**Delivery.** `venueEvents` is a `GUARANTEED` channel (section 10.7): strictly
ordered, never silently dropped; on overflow the connection receives a terminal
`gap` frame naming `venueEvents` and is closed with 4008. There is no sequence
history for this channel yet, so it appears under `unrecoverable`: recover by
reconnecting, resubscribing (fresh `SNAPSHOT`) and backfilling executions over
REST. `venueEvents` is NOT ordered relative to other channels: a `LIQUIDATION`
frame and the matching `execution` frame may arrive in either order; correlate by
`asOfSeq`, `tradeId` and `orderId`.

**Snapshot ordering guarantee.** The `SNAPSHOT` is built atomically against the
engine state: no frame with `asOfSeq` GREATER than the snapshot's can precede it on
that connection. A frame with `asOfSeq` at or below the snapshot's MAY follow it;
apply the snapshot, then apply subsequent frames idempotently by `eventId`.

#### Type set (11)

`SNAPSHOT`, `STOP_TRADING`, `RESUME_TRADING`, `LIQUIDATION`, `ADL`, `MARGIN_CALL`,
`LIQUIDATION_COMPLETE`, `MEMBER_PARAMS` (revision 3.61), plus the two public advisory
types `STALE_MARK_WARNING` and `STALE_MARK_RECOVERED` and the private advisory type
`MARGIN_WATCH` (revision 3.70).

#### Enumerations (complete value sets)

- `tradingState`: `TRADING` | `HALTED` | `REDUCE_ONLY` | `POST_ONLY` | `DELISTED`
- `reason` on `STOP_TRADING`: `ADMINISTRATIVE` (venue operator action, or a
  venue-wide halt) | `CIRCUIT_BREAKER` (automatic own-traded-price volatility halt)
  | `STALE_MARK_PRICE` (mark/index feed exceeded the staleness timeout; reduce-only
  until it recovers) | `DELISTING` (permanent withdrawal of that listing; all
  resting orders cancelled; after a re-listing the same symbol can return as a
  new market, section 2a)
- `reason` on `RESUME_TRADING`: `ADMINISTRATIVE` | `CIRCUIT_BREAKER_EXPIRY`
  (cooldown elapsed, automatic resume) | `MARK_PRICE_RECOVERED` (fresh marks
  resumed, reduce-only window lifted)
- `scope`: `INSTRUMENT` | `VENUE`
- `role` (ADL only): `DELEVERAGED` | `DEFAULTER`
- `state` (MARGIN_CALL only): `ACTIVE` | `CLEARED`
- `riskStatus`: `NORMAL` | `MARGIN_CALL` | `LIQUIDATION` | `DEFAULTED`
- `memberStatus`: `ACTIVE` | `POST_ONLY` | `REDUCE_ONLY` | `SUSPENDED`
- `trigger` (LIQUIDATION_COMPLETE only): `MARGIN_BREACH` | `ADMIN_FORCED`
- `side`: `BUY` | `SELL` (closed set; `null` is never emitted)

Forward compatibility: the venue may add `reason` values. Treat an unrecognised
`reason` on `STOP_TRADING` as a stop you may not clear on your own, and an
unrecognised `reason` on `RESUME_TRADING` as a normal resume. `reason` is `null`
only inside `SNAPSHOT` rows whose `tradingState` is `TRADING`.

**Legacy spelling table.** The spellings above are NEW and deliberately differ from
the older surfaces, which stringify enums directly and are frozen for deployed
clients. Do not feed one surface's values into a switch written for the other:

| Legacy surface | Legacy value | `venueEvents` value |
|---|---|---|
| `GET /v1/status` `instruments[].status`, `status.{symbol}` WS channel | `ACTIVE` | `TRADING` |
| | `HALTED` | `HALTED` |
| | `REDUCEONLY` | `REDUCE_ONLY` |
| | `POSTONLYSESSION` | `POST_ONLY` |
| | `DELISTED` | `DELISTED` |
| `GET /v1/account` `riskStatus` | `MARGINCALL` | `MARGIN_CALL` |
| `GET /v1/status` `venue` | `ACTIVE` | `TRADING` |

#### STOP_TRADING / RESUME_TRADING

Emitted on any transition INTO a non-TRADING state, including a transition between
two non-TRADING states (you always hold the current state); `RESUME_TRADING` only
on a transition INTO `TRADING`, so its `tradingState` is always `"TRADING"`. A
`STOP_TRADING` is also re-issued for a market that is already stopped when the
`reason` changes under it, which happens when venue operations take over a halt the
circuit breaker had raised: the message repeats the current `tradingState` with the
new `reason` and `expectedResumeTs: null`, and it SUPERSEDES the earlier message for
that market. Apart from that, no message is emitted when the state does not change.
A venue-wide halt does not suppress instrument-level messages or vice versa:
effective tradability is `venue AND instrument`. `STOP_TRADING` with
`tradingState: "REDUCE_ONLY"` means stop opening; closing orders are still accepted.

`STOP_TRADING.data`: `scope`, `symbols` (one element for `INSTRUMENT` scope; every
non-delisted instrument, ascending, for `VENUE` scope), `tradingState`, `reason`,
`triggerPrice` / `referencePrice` / `expectedResumeTs` (non-null only when `reason`
is `CIRCUIT_BREAKER`, and `expectedResumeTs` additionally only when that halt has an
automatic resume time: a breaker halt raised while the market was in `REDUCE_ONLY` or
`POST_ONLY` has none, section 6), `cancelsResting` (true only for `DELISTING`).

```json
{"channel":"venueEvents","type":"STOP_TRADING","eventId":"904118-2","asOfSeq":904118,"ts":1785000123456,
 "data":{"scope":"INSTRUMENT","symbols":["BTC-USD-PERP"],"tradingState":"HALTED","reason":"CIRCUIT_BREAKER",
 "triggerPrice":"71850.5","referencePrice":"68000","expectedResumeTs":1785000243456,"cancelsResting":false}}
```

`RESUME_TRADING.data`: `scope`, `symbols`, `tradingState` (always `"TRADING"`),
`reason`.

```json
{"channel":"venueEvents","type":"RESUME_TRADING","eventId":"904360-1","asOfSeq":904360,"ts":1785000243500,
 "data":{"scope":"INSTRUMENT","symbols":["BTC-USD-PERP"],"tradingState":"TRADING","reason":"CIRCUIT_BREAKER_EXPIRY"}}
```

#### STALE_MARK_WARNING / STALE_MARK_RECOVERED (advisory, both sockets)

Advisory early warning for the mark-price staleness ladder. Every market with a
staleness policy has a per-market staleness threshold; the ladder has three
steps:

1. **Warning at half the threshold.** When the age of the market's last mark
   price tick crosses HALF the threshold, the venue broadcasts
   `STALE_MARK_WARNING`. The market is still fully tradable at this point;
   nothing about the trading state has changed.
2. **Reduce-only at the threshold.** If the feed is still silent when the age
   reaches the full threshold, the market enters reduce-only through an
   ordinary `STOP_TRADING` with `tradingState: "REDUCE_ONLY"` and
   `reason: "STALE_MARK_PRICE"` (documented above).
3. **Automatic recovery.** When fresh ticks resume, the market leaves
   reduce-only automatically (`RESUME_TRADING` with
   `reason: "MARK_PRICE_RECOVERED"`), and `STALE_MARK_RECOVERED` is broadcast
   once the tick age is back under half the threshold. When the feed recovers
   before the market ever flipped, no `STOP_TRADING` or `RESUME_TRADING` is
   emitted and `STALE_MARK_RECOVERED` alone closes the episode.

`STALE_MARK_WARNING.data`: `symbol`, `tickAgeSec` (integer seconds since the
last mark price tick at the moment the warning fired), `reduceOnlyInSec`
(integer seconds remaining until the market reaches the reduce-only threshold;
`0` when the threshold has already been reached or passed).

`STALE_MARK_RECOVERED.data`: `symbol`, `tickAgeSec`.

```json
{"channel":"venueEvents","type":"STALE_MARK_WARNING","eventId":"advisory-905220-1","asOfSeq":905220,"ts":1785000501000,
 "data":{"symbol":"BTC-USD-PERP","tickAgeSec":151,"reduceOnlyInSec":149}}
```

```json
{"channel":"venueEvents","type":"STALE_MARK_RECOVERED","eventId":"advisory-905388-1","asOfSeq":905388,"ts":1785000643000,
 "data":{"symbol":"BTC-USD-PERP","tickAgeSec":2}}
```

**Advisory semantics.** These two types are best-effort advisories, not engine
events:

- They are informational only. Never drive your trading-state machine off them:
  trading-state transitions arrive exclusively as `STOP_TRADING`,
  `RESUME_TRADING` and `SNAPSHOT`.
- They are not journaled and never appear in the `SNAPSHOT`, in
  `GET /v1/stream/events`, or in any backfill or replay. A subscriber that
  connects mid-episode learns of the situation from the snapshot only after
  the market has actually flipped (`reason: "STALE_MARK_PRICE"`).
- One `STALE_MARK_WARNING` is emitted per staleness episode. A venue restart
  or failover BEFORE the market flipped to reduce-only may repeat the warning
  once, with a new `eventId`; treat a repeat as a reminder about the same
  episode, not as a new one. A restart or failover landing AFTER the flip
  emits NO late warning for that episode: a market the venue already holds in
  reduce-only never warns again, so the warning-precedes-flip ordering below
  holds across restarts too. A subscriber joining mid-episode learns the
  reduce-only state from the `SNAPSHOT`, and the episode still ends with
  `STALE_MARK_RECOVERED`.
- For one episode the warning always precedes any
  `STOP_TRADING`/`STALE_MARK_PRICE` for that market on this channel, and
  `STALE_MARK_RECOVERED` follows the matching `RESUME_TRADING` when the market
  did flip.
- Markets without a staleness policy never warn.

**Note for brokers and market makers.** Treat `STALE_MARK_WARNING` as the
signal to widen quotes or pause quoting on that market: the mark price is no
longer moving, and `reduceOnlyInSec` tells you how long remains before opening
orders start being refused on it. `STALE_MARK_RECOVERED` is the all-clear.
Staleness also pauses automatic liquidation DETECTION for any member with
exposure on the stale market (section 7): a margin breach re-detects on the
first sweep after recovery, so expect liquidation activity to resume together
with the mark, not during the outage. Funding on the stale market waits too
(section 7, revision 3.70): no anchor is settled until the mark recovers, then
every waiting anchor settles at the fresh mark on the positions held at that
settlement, so expect the funding ledger entries and the `RESUME_TRADING` to
arrive together.

#### LIQUIDATION (private, to the liquidated member only)

One event per venue-initiated liquidation execution against your account. The
resting counterparty of a liquidation trade receives an ordinary `execution`,
never this event.

`data`: `episodeId` (int64 or null), `symbol`, `side` (the side the venue traded on
your behalf, so `SELL` closes a long), `qty`, `price` (decimal strings), `tradeId`,
`orderId` (the venue's synthetic order id; correlates with the `execution`
channel), `clientUid`, `positionRef`, `clOrdId` (always null), `fee`.

`positionRef` is `clientUid + "|" + symbol` and is THE field that resolves a
venue-initiated close to the correct end-client position. A venue-initiated close
carries no client order reference (`clOrdId` is null by construction); use
`positionRef`.

```json
{"channel":"venueEvents","type":"LIQUIDATION","eventId":"904512-9","asOfSeq":904512,"ts":1785000301222,
 "data":{"episodeId":41,"symbol":"BTC-USD-PERP","side":"SELL","qty":"3","price":"67660",
 "tradeId":88214,"orderId":775401,"clientUid":"7F3A9C21D0B4","positionRef":"7F3A9C21D0B4|BTC-USD-PERP",
 "clOrdId":null,"fee":"101.49"}}
```

#### ADL (private)

`data`: `episodeId`, `role`, `symbol`, `side`, `qty`, `price` (the bankruptcy
price, capped in extreme conditions to the nearest representable price, section
7; both legs settle at the same price), `tradeId`, `clientUid` (string or null),
`positionRef` (string or null), `clOrdId` (always null).

- `role: "DELEVERAGED"`: your opposite-side position (profitable positions
  first) was force-closed against a defaulting member. Fully client-resolved
  (`clientUid`, `positionRef` present).
- `role: "DEFAULTER"`: you are the member being unwound. `clientUid` and
  `positionRef` are null here and this frame stays exactly one per trade: one
  ADL trade settles across several of your end clients pro rata and the
  per-client split is on the `adl` channel (event class, cursor-backfillable,
  section 11: one `execution` frame per end client with `role` `DEFAULTER`, your
  `clientUid`, `positionSide`, `qty`, `price`, `tradeId` and `episodeId`, revision
  3.70, delivered from the engine sequence venue operations announce) and on
  `GET /v1/adl/events` (section 7).

You are never both roles in the same episode.

```json
{"channel":"venueEvents","type":"ADL","eventId":"904690-14","asOfSeq":904690,"ts":1785000355900,
 "data":{"episodeId":42,"role":"DELEVERAGED","symbol":"BTC-USD-PERP","side":"SELL","qty":"5","price":"66120",
 "tradeId":88301,"clientUid":"1C88E4A7B2F0","positionRef":"1C88E4A7B2F0|BTC-USD-PERP","clOrdId":null}}
```

#### MARGIN_CALL (private)

Emitted on every change of your `riskStatus` (this is the member risk-status
stream; the name follows the requirement heading). `state` is `"CLEARED"` when the
new `riskStatus` is `NORMAL`, `"ACTIVE"` otherwise (`LIQUIDATION` and `DEFAULTED`
are both `ACTIVE`). The transition into `LIQUIDATION` is the "your unwind has
started" signal.

While `riskStatus` is `LIQUIDATION` or `DEFAULTED` the venue refuses your order
placement and amends with `3002` on risk status alone, whatever `memberStatus`
shows; cancels stay accepted, and withdrawals are refused for the same period.
`DEFAULTED` ends only when venue operations clear the default after repayment.

`data`: `state`, `riskStatus`, `ccy`, `equity`, `initialMargin` (the `margin`
channel's `im`), `maintenanceMargin` (`mm`), `available` (clamped at zero exactly
like `GET /v1/account`), `marginRatio` (identical computation to
`GET /v1/account`; null when equity is not positive).

Note: `riskStatus` transitions that begin and end inside one engine command are
collapsed to the net change; the stream is not a complete transition log.

```json
{"channel":"venueEvents","type":"MARGIN_CALL","eventId":"904480-6","asOfSeq":904480,"ts":1785000299000,
 "data":{"state":"ACTIVE","riskStatus":"MARGIN_CALL","ccy":"USDC","equity":"181450.22",
 "initialMargin":"204000","maintenanceMargin":"102000","available":"0","marginRatio":"0.562136676"}}
```

#### LIQUIDATION_COMPLETE (private, to the unwound member only)

Terminal message of an unwind episode. An episode can cancel your entire book and
cure without producing a single execution; this message is then the only place the
venue states what it did.

`data`: `episodeId`, `trigger`, `symbols` (every instrument the episode touched),
`ordersCanceled`, `executionCount`, `insuranceFundDelta` (decimal string),
`residualLoss` (decimal string), `riskStatus`, `memberStatus`.

```json
{"channel":"venueEvents","type":"LIQUIDATION_COMPLETE","eventId":"904702-3","asOfSeq":904702,"ts":1785000356400,
 "data":{"episodeId":42,"trigger":"MARGIN_BREACH","symbols":["BTC-USD-PERP","ETH-USD-PERP"],
 "ordersCanceled":37,"executionCount":11,"insuranceFundDelta":"0","residualLoss":"12400.5",
 "riskStatus":"DEFAULTED","memberStatus":"SUSPENDED"}}
```

#### MEMBER_PARAMS (private, revision 3.61)

One frame per change of your own parameters (section 7, `GET /v1/account/parameters`).
`data`: `scope` (`ACCOUNT` for the account-wide values, `MARKET` for one market),
`symbol` (the market, null at account scope), `origin` (`OPERATOR` for a value
venue operations set, `MEMBER` for one you set yourself on
`PUT /v1/account/parameters`, revision 3.64), `set` (the parameter
names written) and `cleared` (the names reset to inherit). The frame carries the
NAMES only: re-read `GET /v1/account/parameters` for the effective values, which
the venue resolves from every scope. Delivered only to the member concerned.

```json
{"channel":"venueEvents","type":"MEMBER_PARAMS","eventId":"904800-3","asOfSeq":904800,"ts":1785000500000,
 "data":{"scope":"MARKET","symbol":"BTC-USD-PERP","origin":"OPERATOR","set":["priceBandBps"],"cleared":[]}}
```

#### MARGIN_WATCH (private, advisory, revision 3.70)

The venue's margin utilisation early warning, pushed to you the moment your account
crosses it or clears it. Utilisation is your initial margin plus order margin over
your equity in the settlement currency (the same figures as `GET /v1/account`);
the level is the venue's published early-warning threshold, or the one venue
operations set for your account. `data`: `state` (`ABOVE` when utilisation reached
the threshold, `CLEARED` once it fell back strictly below `reArmPct`), `ccy`,
`utilisationPct`, `thresholdPct`, `reArmPct` (decimal strings, percent, at most two
decimals). ADVISORY ONLY: this is an early warning, not a margin call; the engine's
`MARGIN_CALL` ladder and its liquidation rules are unchanged and unaffected by it.
One frame per transition, bounded by the hysteresis between `thresholdPct` and
`reArmPct` (never by the notice cooldown that bounds the case and the mail venue
operations receive for the same crossing). Best effort like every advisory: never
journaled, never in the `SNAPSHOT`, never on `GET /v1/stream/events` or any
backfill; after a venue restart or failover an account still above the level
receives `ABOVE` once more on the first pass. Delivered only to the member
concerned, on authenticated private sessions.

```json
{"channel":"venueEvents","type":"MARGIN_WATCH","eventId":"advisory-904900-17","asOfSeq":904900,"ts":1785000600000,
 "data":{"state":"ABOVE","ccy":"USD","utilisationPct":"82.5","thresholdPct":"80","reArmPct":"75"}}
```

#### SNAPSHOT (sent once, on subscribe)

`data`: `venue` (object), `instruments` (array), `member` (object, or null on a
public or unauthenticated session).

- `venue`: `tradingState` (`TRADING` or `HALTED`), `reason` (or null), `since`
  (int64 ms or null).
- `instruments[]`: `symbol`, `tradingState`, `reason` (null when `TRADING`),
  `since`, `expectedResumeTs`.
- `member`: `riskStatus`, `openEpisodeId` (int64 or null).

`since` is the `ts` of the message that put the instrument into its current state;
it is null for a state that predates the current venue process.

```json
{"channel":"venueEvents","type":"SNAPSHOT","eventId":"snapshot-904118","asOfSeq":904118,"ts":1785000400000,
 "data":{
   "venue":{"tradingState":"TRADING","reason":null,"since":null},
   "instruments":[
     {"symbol":"BTC-USD-PERP","tradingState":"HALTED","reason":"CIRCUIT_BREAKER","since":1785000123456,"expectedResumeTs":1785000243456},
     {"symbol":"ETH-USD-PERP","tradingState":"TRADING","reason":null,"since":null,"expectedResumeTs":null}],
   "member":{"riskStatus":"NORMAL","openEpisodeId":null}}}
```

## 11. Event stream recovery (REST backfill)

Every event-class message the private socket delivers is also written to a durable,
sequence-ordered event store and can be re-read over REST. This is how you recover
from a disconnect, a slow-consumer close (4008), or your own downtime without
losing a single notification.

### 11.1 Event class vs state class

Every channel's messages are one of two kinds:

- **Event-class** (durable, backfillable by cursor): `order`, `execution`,
  `wallet`, `margin` (type `marginCall`), `adl` (type `execution`), `unwind`,
  `eligibility`, `funding` (type `settlement`), `status` (instrument and venue
  transitions), `swaps` (revision 3.51, section 6h). One stored row per delivered message; a backfilled `data` object
  is byte-identical to the live frame's `data`. The `adl` row's `data` is the
  execution frame's `{episodeId, tradeId, symbol, qty, price, qtyLots, priceTicks,
  clientUid, positionSide (omitted for NET), role, side, ts}` (revision 3.70; a
  defaulting member's own per-client rows, `role` `DEFAULTER`, are stored and
  backfilled like the counterparty's, from the engine sequence venue operations
  announce; rows stored before the revision carry no `role` or `side`).
- **State-class** (each message supersedes the last; recover with a snapshot
  read, never a cursor): `position`, `clientPosition`, `margin` (type `update`),
  `adl` (type `indicator`), `orderbook.*`, `matchableBook.*`, `ticker.*`,
  `trades.*`, `markIndex.*`, `funding.*` (type `rate`). Recovery reads are listed
  in the table in section 10.8.

**Never backfillable, by design:** the synchronous rejection of a duplicate
`clOrdId` (it reaches you only as the order response, with `duplicate: true`),
`venueEvents` frames (resubscribe and apply the fresh `SNAPSHOT`), and the `rfq`
and `barriers` channels (state-only: re-read `GET /v1/rfq/mine`,
`GET /v1/rfq/open` and `GET /v1/barriers`). Do not wait for any of these to
appear in the stream.

### 11.2 The cursor

```
cursor = seq (19 digits) + eventIndex (10 digits) + leg (1 digit)
```

30 zero-padded numeric ASCII characters, e.g. seq=104871, eventIndex=4, leg=0 →
`"000000000000010487100000000040"`. Properties you may rely on:

- Fixed width: ordinal string comparison equals engine event order in any
  language. No parsing needed.
- The same delivery has the same cursor live and on backfill, across venue
  restarts and store rebuilds.
- Cursors are strictly increasing in delivery order but NOT contiguous for a
  given member (other members' events consume sequence numbers too).
- `leg` is the 0-based ordinal of the delivery produced by one engine event: 0
  for everything except executions, where leg 0 is the MAKER leg and leg 1 the
  TAKER leg. When one trade fills two of your own end clients you receive both
  legs, at the same (`asOfSeq`, `eventIndex`), distinguished by `role`/leg.
- Live frames carry (`asOfSeq`, `eventIndex`); derive the live cursor as
  (`asOfSeq`, `eventIndex`, leg) with leg = 1 for `execution` frames whose
  `role` is `TAKER` and 0 otherwise.

A cursor whose `eventIndex` is 2147483647 and whose `leg` is 9 is a BOUND, not a
delivery: the venue returns it as `fromCursor` for a `fromSeq` request and, from
revision 3.73, as `nextCursor` on a last page. Every delivery at that `seq` sorts
below it, every delivery at the next `seq` above it. The rule "the same delivery
has the same cursor" holds for deliveries. The venue returns a last page's
bound where it serves the last-page position (section 11.3). Pass a bound back
unchanged as `afterCursor`; no event ever carries it.

**The field ranges (revision 3.83 states them; nothing changed).** `seq` is a
signed 64-bit integer, 0 to 9223372036854775807; `eventIndex` is a signed
32-bit integer, 0 to 2147483647; `leg` is one digit, 0 to 9 (deliveries use 0
and 1, and 9 marks a bound). Ten digits is the padded width of a 32-bit value,
not its range. `eventIndex` is not a running counter: it is the 0-based
position of an event among the events of ONE engine command, and it restarts at
0 for every `seq`; you receive only your own events, so within one `seq` the
values you see increase but are not contiguous. The engine, the event store,
the live frames and FIX `ExecID(17)` carry the same value, and the venue's
limits keep a real one far below 2147483647 (each entry of a batch is a command
of its own). A well-formed 30-digit `afterCursor` whose `seq` or `eventIndex`
field lies above its range answers `2019`, and since revision 3.83 its `msg`
names the field and the range:
`afterCursor seq (characters 1 to 19) is out of range: it must be 0 to 9223372036854775807`
or
`afterCursor eventIndex (characters 20 to 29) is out of range: it must be 0 to 2147483647`,
both joined by `; ` when both fields are; anything that is not 30 ASCII digits
answers `afterCursor must be 30 numeric characters`, the text every malformed
cursor answered before. Branch on the code, never on the `msg`. Store cursors
as the 30-character strings the venue returns and compare them as strings; when
you derive one from a live frame, zero-pad the fields to 19, 10 and 1 digits.

### 11.3 `GET /v1/stream/events` (private)

Query parameters (exactly ONE of `afterCursor` / `fromSeq` is required):

| Param | Meaning |
|---|---|
| `afterCursor` | 30-char cursor of 30 ASCII digits, EXCLUSIVE: returns strictly greater deliveries; a field above its range (section 11.2) is `2019` |
| `fromSeq` | int64 >= 1, INCLUSIVE lower bound on `seq` (convenience for a client that kept only `asOfSeq`) |
| `toSeq` | optional int64 >= 1, INCLUSIVE upper bound on `seq` |
| `channels` | optional CSV of the ten event-class families (`order`, `execution`, `wallet`, `margin`, `adl`, `unwind`, `eligibility`, `funding`, `status`, `swaps`; default: all ten; anything else is `2019`; from revision 3.73 a value that names no family (empty, commas only) is `5003`) |
| `symbol` | optional; excludes rows that are not instrument-scoped (wallet, margin, eligibility, unwind, venue halt/resume) |
| `limit` | default 500, clamped to [1, 2000] |
| `waitMs` | default 0, clamped to [0, 2000]: bounded wait for the store to catch up; the answer is authoritative either way |

Ordering is STRICTLY ASCENDING by (`seq`, `eventIndex`, `leg`); this is the only
ascending history route, deliberately, because a backfill must replay in engine
order. Response `data`:

```json
{"fromCursor":"000000000000010487100000000040",
 "nextCursor":"000000000000010512400000000011",
 "hasMore":true,"count":500,
 "events":[
   {"channel":"execution","eventType":"EXECUTION","asOfSeq":104872,"eventIndex":0,
    "leg":1,"cursor":"000000000000010487200000000001",
    "data":{"tradeId":55123,"orderId":887711,"symbol":"BTC-USD-PERP","side":"BUY",
            "price":"65000","qty":"5","fee":"162.5","role":"TAKER","flags":"NORMAL",
            "clOrdId":"a3f9...","clientUid":"CLIENT-A","ts":1753961234512}}],
 "store":{"contiguousThroughSeq":104870,"engineSeq":104882,"lagSeq":12,
          "status":"CATCHING_UP","complete":false}}
```

- `eventType` is the stable canonical name (`ORDER_ACCEPTED`, `ORDER_REJECTED`,
  `ORDER_CANCELED`, `ORDER_FILLED`, `ORDER_EXPIRED`, `ORDER_AMENDED`,
  `ORDER_TRIGGERED`, `EXECUTION`, `WALLET_ENTRY`, `MARGIN_CALL`, `ADL_EXECUTION`,
  `UNWIND_EPISODE_OPENED`, `UNWIND_EPISODE_CLOSED`, `LIQUIDATION_STARTED`,
  `LIQUIDATION_PROGRESSED`, `LIQUIDATION_ENDED`, `FUNDING_SETTLED`,
  `FUNDING_SETTLEMENT_DETAIL` (LOCAL_DAILY markets only, section 7),
  `INSTRUMENT_STATUS_CHANGED`, `VENUE_HALTED`, `VENUE_RESUMED`). `type` is
  included exactly where the live frame has one and omitted where it does not.
- `nextCursor`: on a page with `hasMore` true, the cursor of the LAST returned
  event. On a page with `hasMore` false (revision 3.73), a position at the store
  watermark the page was read at, or at your `toSeq` when that is lower, never
  lower than your own cursor, valid only with the same `channels` and `symbol`
  (a bound, section 11.2): a filter that matches nothing new then advances
  instead of re-reading the same range. Pass it back as `afterCursor`; stop a
  paging loop on `hasMore` false or `count` 0, never on `nextCursor` equal to your
  `afterCursor`, and detect "nothing new" by `count` 0. Never look an event up by
  `nextCursor` and never assert it equals the last event's `cursor`. The venue
  serves this last-page position on an environment from the date venue
  operations announce for it there (revision 3.83 states the condition, which
  revision 3.73 left out); before that date, and on a page read while the store
  recorded or cleared a hole, a last page answers the rule before revision
  3.73, the cursor of its last event, else the cursor you sent (the `fromSeq`
  bound on a `fromSeq` request), and a `channels`
  value that names no family answers an empty page instead of `5003`. A paging
  loop that stops on `hasMore` false or `count` 0 is correct under both.
- The envelope `asOfSeq` and `store.contiguousThroughSeq` are the STORE WATERMARK,
  not the engine head. No returned event ever has `asOfSeq` above it. `store.status`
  is an exhaustive three-value enum: `COMPLETE` (level with the engine),
  `CATCHING_UP` (poll again with `afterCursor=nextCursor`), `REPAIRING` (the
  store has a recorded hole; reads stall below it rather than skipping it, keep
  polling and raise an operational alert).
- `REPAIRING` in detail (revision 3.83 states it; nothing changed). The block
  describes the store of the NODE that answered. `REPAIRING` starts at every
  leader change of a clustered venue (the new leader records as a hole the
  commands between its store's durable position and its own head), when the
  store falls further behind than its write buffer absorbs, and when a store
  write fails beyond a short retry. No timeout ends it: it ends only when the
  hole is re-folded from the venue's journal. After a leader change the new
  leader runs that repair itself, and venue operations take over when it does
  not close the hole; venue operations repair every other hole, one that opens
  on a running leader included. The venue sets no maximum duration, and the
  older the hole, the longer its repair. Meanwhile `contiguousThroughSeq` and
  the envelope `asOfSeq` stop just below the hole, and `lagSeq` counts the
  commands from the start of the hole to the engine head, not the size of the
  hole, so it grows while the hole stands. `GET /v1/executions` and
  `GET /v1/account/ledger` are held below the hole too (section 4, revision
  3.83). A node that is not the leader answers the event stream reads from its
  own store position, which no longer advances (`CATCHING_UP` with a growing
  `lagSeq`, or `REPAIRING` if it was demoted with a hole standing); while it
  records no hole, its `GET /v1/executions` and `GET /v1/account/ledger` read
  the venue's shared store whole, so their rows can run ahead of the `asOfSeq`
  it answers. It closes the connection after its answer, so your next request
  reaches the leader (section 10.7).
- `ORDER_REJECTED` payloads carry `data.rejectCode` (int, section 9 registry). A
  pre-book reject legitimately has `"orderId": 0` (the rejection happened before an
  order id was assigned; correlate by `clOrdId`), but `clientUid` is still present:
  it is stamped from the submitted order on every rejection. It is empty only when
  the submission itself omitted it, which is in turn rejected with 2010 unless the
  member trades house flow.
- Because the live socket flush runs marginally ahead of the store write, a
  backfill issued microseconds after a live message can legitimately come back
  short with `status: "CATCHING_UP"`; that is what `lagSeq` and `waitMs` are for.

Errors: 5003 (parameter errors, message names the parameter), 2019 (malformed
cursor or unknown channel), 2001 (unknown symbol), 5004/HTTP 503 (deployment
without an event store), 5007/HTTP 503 (revision 3.73: the venue's history
store did not answer in time, is held after a slow read, or is catching up;
retry the same read after `retryAfterMs`), 1001/1002/1003 as on every private
route, and 1003/HTTP 429 also when more reads of yours of the event stream
group are in flight than the node serves at once, or when the node's shared
budget of these reads is full (section 4; `retryAfterMs` is then a suggested
wait); on these routes HTTP 503 carries `5004` (this
deployment has no event store: permanent) or `5007` (retry after
`retryAfterMs`): branch on the code.

**Migration table, revision 3.73** (dated 28 September 2026; the position cursor
on the last page is a correction in force at publication by the operator's
decision of 2026-09-28, the venue being before production go-live, the 30-day
notice of the versioning policy waived for it; this table is the guide of what to
do):

| What you do | Before revision 3.73 | From revision 3.73 | What to do |
|---|---|---|---|
| read a page of `GET /v1/stream/events` with `hasMore` false that carries events | `nextCursor` = the cursor of the last event | `nextCursor` = the position at the watermark (or `toSeq`), at or above the last event's cursor | pass `nextCursor` back as `afterCursor` as before; never look an event up by it and never assert it equals the last event's `cursor` |
| read an empty page | `nextCursor` = your `afterCursor` (or the `fromSeq` bound) | the position at the watermark (or `toSeq`) when that is above your cursor, else your cursor | as above; detect "nothing new" by `count` 0, never by cursor equality |
| page until `nextCursor` equals the `afterCursor` you sent | the loop stops on the first empty page | the loop does not stop while the venue trades (the watermark moves between calls) | stop on `hasMore` false or `count` 0 |
| persist only the cursor of the last event you processed and resume from it after a restart | the same position as `nextCursor` of an empty page | a sparse filter re-reads from that event again at every restart | persist the `nextCursor` of your last page per filter; resume from it, or from the last processed event's cursor when that is higher (a live frame can be ahead of the store); use event cursors to deduplicate (sections 10.8 and 11.6) |
| reuse a cursor read under one `channels` or `symbol` filter with another filter, or discard buffered live frames of families you did not backfill by it | the same hazard on pages that carried events | the cursor passes every seq up to the watermark | keep one cursor per filter; discard buffered frames by your backfill cursor only for the families that backfill read (section 11.6) |
| send `channels` naming no family (an empty value, commas only) | an empty page | `5003` | omit `channels` for all ten families, or name at least one |

### 11.4 `GET /v1/stream/cursor` (private; WS twin `get.streamCursor`)

```json
{"headCursor":"000000000000010487000000000090","headSeq":104870,
 "retentionFromCursor":"000000000000009800000000000000","retentionFromSeq":98000,
 "store":{"contiguousThroughSeq":104870,"engineSeq":104882,"lagSeq":12,
          "status":"CATCHING_UP","complete":false}}
```

`headCursor` is the newest stream row visible to you at the watermark (omitted
when you have none, in which case `headSeq` is 0). `retentionFromCursor` is the
oldest row still retained (default retention 35 days). If your stored cursor is
older than the retention boundary, a full backfill is impossible: fall back to
snapshot reads plus the dated history routes.

That 35 day window is the event stream's own retention. The venue's record of
every transaction (orders, executions, ledger postings and the contract
lifecycle) is kept for at least seven years from the creation of each record on
a durable medium that keeps it unaltered and replayable, and it is available to
you on request: use the history routes, the ledger and your monthly statements
first, and for anything beyond them write to venue operations, which answers
from the venue's archive with your own records only, never counterparty
identities, within ten Business Days (revision 3.70).

Errors: 5004/HTTP 503 (deployment without an event store), 5007/HTTP 503
(revision 3.73: the venue's history store did not answer in time, is held after
a slow read, or is catching up; retry after `retryAfterMs`), 1001/1002/1003 as
on every private route, and 1003/HTTP 429 also when more reads of yours of the
event stream group are in flight than the node serves at once, or when the
node's shared budget of these reads is full (section 4). The WebSocket twin acks
the same codes (section 10.6). Branch on the code as in section 11.3.

### 11.5 `GET /v1/margin/calls` (private)

Margin-call notice history (see the section 7 table): `from`/`to` (ms,
inclusive), `limit` (default 100, max 1000), newest first, each row carrying the
notice's stream cursor plus `equity`, `im`, `mm`, `available`, `ccy` as exact
decimal strings captured at the moment of the call.

Errors: 5004/HTTP 503 (deployment without an event store), 5007/HTTP 503
(revision 3.73: the venue's history store did not answer in time, is held after
a slow read, or is catching up; retry after `retryAfterMs`), 1001/1002/1003 as
on every private route, and 1003/HTTP 429 also when more reads of yours of the
event stream group are in flight than the node serves at once, or when the
node's shared budget of these reads is full (section 4). Branch on the code as
in section 11.3.

### 11.6 Reconnect recipe

Follow section 10.8. In short: persist the `nextCursor` of the last page of each
filter you backfill with, and the cursor of every event-class message you finish
processing; on reconnect subscribe first and buffer; page `/v1/stream/events`
for each filter from its RESUME POINT, the ordinal maximum of the two (a live
frame's cursor may be ahead of the store, and the venue never moves a cursor
below the one you send), until it reaches the buffer; stop a paging loop on
`hasMore` false or `count` 0, never on `nextCursor` equal to your `afterCursor`;
use event cursors to deduplicate; drain the buffer discarding every frame at or
below your cursor (one string comparison plus the section 10.8 dedupe key), but
only for the families that backfill read: a cursor read under one `channels` or
`symbol` filter passes every seq up to the watermark (revision 3.73), so keep one
cursor per filter; re-snapshot the state-class channels you use.

## 12. Quickstart (sandbox)

```bash
# Instruments and book need no auth:
curl "https://sandbox.mtf.perpetuals.net/v1/instruments"
curl "https://sandbox.mtf.perpetuals.net/v1/orderbook?symbol=BTC-USD-PERP&depth=5"
```

Place an order (see §3 for the Python `signed()` helper):

```python
order = signed("POST", "/v1/orders", {
    "symbol": "BTC-USD-PERP", "side": "BUY", "type": "LIMIT", "tif": "GTC",
    "price": "64000", "qty": "1",
    "clOrdId": "test-0001", "clientUid": "CLIENT-A",
})
print(order["data"]["status"], order["data"]["orderId"])
print(signed("GET", "/v1/positions"))
```

Integration checklist:

1. Fetch `/v1/instruments`; align all prices to `tickSize` and all quantities
   to `qtyStep`, and respect `minNotional` (section 2a).
2. Implement the signature recipe; verify against the §3 worked example; sign
   every send, a retry included, with a fresh `X-TIMESTAMP` (§3).
3. Place/cancel on sandbox; handle `duplicate: true` retries and the §9 reject codes.
4. Subscribe WS `orderbook` + verify checksums; log in on `/ws/private` by signing
   the connection's challenge nonce (§10.1) and subscribe private `order`/`execution`.
5. Reconcile end-client sub-positions daily via `/v1/positions/end-clients`.
6. Handle `503 ENGINE_BUSY` (retry with backoff) and venue/instrument status changes;
   branch on the code of an HTTP 503, never on the status alone: `5001`, `5004`,
   `5007` and, on a clustered deployment such as the sandbox, `5030` (section 10.7).

---

## 13. Changelog

| Revision | Date | Changes |
|---|---|---|
| 3.86 | 2026-10-21 | A GTD ORDER NEVER TRADES AT OR AFTER ITS EXPIRETS (issue 301; section 6, the `tif` bullet, the `DELETE /v1/orders/{orderId}` and `PUT /v1/orders/{orderId}` rows and a dated migration table after "Manage orders"; section 9, the `2014` row; `/openapi.json` `info.version` to 2.43.0, the `expireTs` description and the good-til-date example; complete code reference revision 2.43, the `2014` row and the `GTD_EXPIRED` value; Member Technical Handbook 2.22, the `GTD` row; FIX rules of engagement 2.31, `ExpireTime(126)` and the `OrderCancelReject(9)` exception of a `35=G` and a `35=F`; the FIX integration manual for Claude Code, the same exception; the documentation site). Found by a member-side test of the sandbox and verified against the engine on 2026-10-20 (identical on the sandbox release); items (1) to (3) and (5) are corrections published at this revision by the operator's decision of 2026-10-20, the venue being before production go-live, the 30-day notice of the versioning policy waived for them, as for item (3) of revision 3.73, revision 3.81 and revision 3.82; the matching engine applies items (1) to (3) from its replay-contract line for this release (the first command of a venue started on the production contract; venue operations announce the date on any environment where the line lies ahead), and item (5) applies from the release that carries this revision. No field, enum, code or default value is added, removed or renamed. (1) Until that line only the venue's expiry sweep, which runs about once a second, expired a `GTD` order, and nothing on the matching path compared the order's `expireTs` with the time of the command reaching it, so an expired `GTD` order could still trade until the sweep reached it: up to about a second after its `expireTs` (fills were observed up to 800 ms late, and at exactly `expireTs`), longer while the venue's timers lagged; the documents said the order rests until `expireTs`. From that line a `GTD` order never trades at or after its `expireTs`, measured against the time the venue sequences each command, never your clock: an incoming order (a `FOK` counts nothing against it, a `POST_ONLY` that would cross only it rests, a `MARKET` order's protection collar is taken from the best order that has not expired) or a call auction's uncross that reaches it expires it with status `EXPIRED`, the report the sweep sends, and goes on to the next order in the book. (2) A conditional `GTD` order whose trigger price is reached at or after its `expireTs` expires instead of triggering. (3) An amendment or a cancellation of a `GTD` order at or after its `expireTs` answers `2014`, the answer it received once the sweep had run, and the order is reported `EXPIRED`, and a cancel-all reports such an order `EXPIRED` (`GTD_EXPIRED`) and counts it in `canceled`; before, it could be amended into a fresh life and trade, and a cancellation or a cancel-all ended it `CANCELED` (`USER_REQUESTED`), so its end reason depended on whether the request or the sweep reached it first (the cancellation by the operator's decision of 2026-10-21). (4) Unchanged: an order nothing reaches is reported `EXPIRED` by the sweep within about a second, and `expireTs` must still be in the future when the venue sequences the order (`2013`). (5) FIX: an `OrderCancelReject(9)` to a `35=G` that the matching engine refuses `2014` carries `CxlRejReason(102)=1` (unknown order), as a `35=F` does; before, an order that ended between the venue's lookup and the replace was refused with `102=0`. |
| 3.85 | 2026-10-21 | REPLAY PROTECTION: A SIGNED WRITE IS ACCEPTED ONCE, THE RECEIVE WINDOW IS REFUSED RATHER THAN REDUCED, RFQ REQUESTS AND QUOTES ARE IDEMPOTENT ON THEIR REFERENCE, AND THE WEBSOCKET LOGIN SIGNS A SERVER CHALLENGE (sections 2, 3, 6b, 9, 10.1, 10.6 and 12, with dated migration tables in sections 3 and 10.1; `/openapi.json` `info.version` to 2.42.0; complete code reference revision 2.42, the `1001`, `1002` and `2009` rows and the retry matrix; Member Technical Handbook 2.21; Integration Pack 3.18; Conformance Test Plan 1.7; the REST and WebSocket manuals, the Postman guide and the documentation site; no FIX change). Found by a member-side test of the sandbox and verified against the venue on 2026-10-20: a signed request captured from inside TLS (a terminating proxy, request logging, a compromised host) was accepted again for as long as its timestamp stayed inside the window, up to 60 seconds, because `X-RECV-WINDOW` is not signed and was reduced to 60000 rather than refused and nothing recorded what had been accepted, so a replayed amend set a resting order back to its old price, a replayed quote came back with a fresh validity and was traded at the stale price, a replayed batch placed its orders twice, and a captured WebSocket `auth` frame opened a second connection with full trading authority until that socket closed. Corrections in force at publication by the operator's decision of 2026-10-20, the venue being before production go-live, the 30-day notice of the versioning policy waived for them, as for item (3) of revision 3.73, revision 3.81, revision 3.82, revision 3.83 and revision 3.84; the WebSocket challenge is mandatory from this release. (1) A SIGNED WRITE IS ACCEPTED ONCE (section 3): the venue records the (`X-API-KEY`, `X-SIGNATURE`) pair of every signed request other than a `GET` once its signature verifies, before the request runs, and keeps it for 60 seconds past its `X-TIMESTAMP`; the same pair again answers HTTP 401, code `1002`, `msg` `request already received: a signed write is accepted once, sign every request (a retry included) with a fresh X-TIMESTAMP`, nothing of it runs and nothing is charged; when a pair leaves the record the node refuses every signed write of the same member stamped at or before it, `1002`, `msg` `request signed before the oldest one this node still records: sign it again with a fresh X-TIMESTAMP`, so no pair is admitted twice. Sign every send, a retry included, afresh and keep its `clOrdId`; two intentionally identical writes need different timestamps. A `GET` is never recorded, and neither is `POST /v1/margin/preview`. Each node keeps its own record and only the leader of a cluster accepts a write, and (2) THE START FENCE: a node refuses `1002`, `msg` `request signed before this node began to accept writes: sign it again with a fresh X-TIMESTAMP`, a signed write stamped before its start or its promotion to leader, so a restart reopens nothing for a client whose clock is synchronised with the venue's, and a leader change nothing beyond any difference between the clocks of the old and the new leader node (section 3). (3) THE RECEIVE WINDOW (section 3): `X-RECV-WINDOW`, still optional and still unsigned, must be an integer from 1 to 60000 (absent or empty: 5000); a larger value, 0, a negative value or one that is not an integer is refused `1002` with `msg` `X-RECV-WINDOW must be an integer from 1 to 60000 (milliseconds); a larger value is refused, not reduced` (before, a larger value was reduced to 60000, 0 and negatives to 0, and a value that was not an integer replaced by 5000); and the window is asymmetric: `X-TIMESTAMP` may lag the venue clock by up to the window and lead it by at most the smaller of the window and 5000 ms, `msg` `timestamp ahead of the venue clock by more than the window allows (at most 5000 ms ahead)` (before, a stamp up to the window ahead, 60 s at most, was admitted); the window is checked when the headers arrive and again once the whole body has arrived (before, only when the headers arrived). (4) RFQ REQUESTS AND QUOTES ARE IDEMPOTENT ON THEIR REFERENCE (sections 2 and 6b): a request (`POST /v1/rfq`: an ordinary request, a barrier close request, a yield contract offer) or a quote (`POST /v1/rfq/{rfqId}/quotes`) whose `clOrdId` you already used, on an order, a swap take, a request or a quote, answers `2009`, `msg` `clOrdId already used: an RFQ request or quote is never restated, read GET /v1/rfq/mine or GET /v1/rfq/quotes/mine`, and opens or replaces nothing (`2045` on a hash collision with another reference of yours); there is no restatement of the original, read `GET /v1/rfq/mine` or `GET /v1/rfq/quotes/mine`; an order under a reference a request, a quote or a swap take of yours holds answers `2009` too, never a restatement; a re-quote that replaces your live quote carries a new `clOrdId`. Applied by the matching engine from its replay-contract line for this release (from the first command of a venue started on the production contract; venue operations announce the date on any environment where the line lies ahead). Sections 2 and 6b keep the statement of revision 3.83, that RFQ creation is not idempotent on its reference, for the commands before that line. (5) THE WEBSOCKET LOGIN SIGNS A SERVER CHALLENGE (section 10.1, with its migration table): the first frame of every `/ws/private` connection is `{"channel":"challenge","type":"challenge","nonce":"<32 lowercase hex>","ts":<venue ms>}`; the `auth` op carries four args, `[apiKey, expiresMs, signature, nonce]`, the signature `hex(HMAC_SHA256(secret, "GET/ws/private" + expiresMs + nonce))` (worked example in section 10.1); a frame without the nonce (the three-arg frame of every earlier revision) answers `1001` `auth failed: sign the nonce of this connection's challenge frame and send it as the fourth arg (Member API section 10.1)`, a frame naming another connection's nonce `1001` `auth failed: the nonce is not this connection's challenge`, so a captured frame opens no other connection; the `expiresMs` bound and every other rule of the frame are unchanged, the nonce lives as long as its connection (a second login on it signs it again) and `/ws/public` sends no challenge. WHAT TO CHANGE IN YOUR WEBSOCKET LOGIN: after the connect, read frames until the one whose `channel` is `challenge` and keep its `nonce`; sign `"GET/ws/private" + expiresMs + nonce` instead of `"GET/ws/private" + expiresMs`; send the nonce as the fourth element of `args`; on every reconnect do the same with the new connection's nonce, never an old one. Every request that worked at revision 3.84 works identically except where items (1) to (5) change an answer. Pinned by `ReplayProtectionWireTests`, `WsLoginChallengeTests`, `ReplayGuardTests`, `SignatureVectorTests`, `RfqClOrdIdDedupTests` and `ReplayProtectionDocsTests`. |
| 3.84 | 2026-10-21 | THE CANCEL-ALL READS ITS FILTERS STRICTLY (sections 6 and 10.5, with a dated migration table after the table of section 6, "Manage orders"; `/openapi.json` `info.version` 2.41.0, the `cancelOrders` operation's description, parameters and a new 400 answer, and the `cancelOrder` operation's description and a new 400 answer; complete code reference revision 2.41, the `2013`, `2017` and `5003` rows; Integration Pack revision 3.17; no FIX change: the venue offers no OrderMassCancelRequest). Every request that worked at revision 3.83 works identically EXCEPT the requests of items (1) to (3) and (5), which `DELETE /v1/orders` and the WebSocket `order.cancelAll` now refuse with nothing cancelled, and an `order.cancelAll` carrying `side`, which now cancels that side only (item (4)). Before, most of the refused requests ran as a cancel of every open order the remaining filters matched, without one every open order of yours on every market, every end client's untriggered conditionals included; the others cancelled less: on `DELETE /v1/orders` a request with `clOrdId` cancelled at most the order `clOrdId` named and a repeated `symbol` or `clOrdId` answered `2001` or `2014`, and on the WebSocket args that were absent, not an array, an empty array or an array whose first element was not an object, and a `symbol` of another type, answered `2013`. Those of items (1) to (3) are corrections in force at publication by the operator's decision of 2026-10-20, the venue being before production go-live, the 30-day notice of the versioning policy waived for them, as for item (3) of revision 3.73, revision 3.81 and revision 3.82. (1) `DELETE /v1/orders` reads the query parameters `clOrdId`, `symbol` and `side` and nothing else, their names matched without regard to case as before: a parameter it does not define (`?clientUid=`, `?orderId=`, a misspelt `?symbl=`), a parameter given twice (before, the values were joined with a comma, so `?side=BUY&side=SELL` read as no side), a `side` other than `BUY` or `SELL` (an empty one included; before, it was no filter and the cancel covered both sides) and `clOrdId` with `symbol` or `side` (before, the two were ignored) answer HTTP 400 with code `5003` and a `msg` naming the parameter, under the rule of item (4) of revision 3.73. (2) A request body on `DELETE /v1/orders` (a JSON object such as `{"clOrdId": ...}` or any other text, with a `Content-Length` or chunked) answers HTTP 400 with code `2017` "DELETE /v1/orders takes no request body: send clOrdId, symbol and side as query parameters; nothing was cancelled"; before, the body was ignored. (3) The WebSocket `order.cancelAll` takes exactly one args object whose only members, if any, are `symbol` and `side` (item (4)), each a string sent at most once: a member it does not define answers `2017` naming it (member names are case-sensitive; a batch body `{"cancels": [...]}` sent to it by mistake is one), a `null` or another JSON type, a member given twice, `args` given twice in the message and args that are not exactly one object (none, `null`, an object, an empty array, two objects) `2013`; before, every other member and every args object after the first were ignored, of two `args` keys the last one was read, and a `null` `symbol` read as absent (args that were absent, not an array, an empty array or an array whose first element was not an object and a `symbol` of another type answered `2013` "malformed request" before too, and only their `msg` changes). Every refusal's `msg` ends "nothing was cancelled". (4) ADDITIVE, by the operator's decision of 2026-10-21: the WebSocket `order.cancelAll` reads `side` with exactly the semantics of `DELETE /v1/orders?side=`: `BUY` or `SELL` in any case cancels the open orders of that side only, with `symbol` that side on that market, and any other string, an empty one included, answers `5003` "side must be BUY or SELL; nothing was cancelled" as on REST; before, the op did not read `side`, and an `order.cancelAll` carrying one cancelled both sides. (5) `DELETE /v1/orders/` with a trailing slash, with or without a query, is a `DELETE /v1/orders/{orderId}` whose `orderId` is empty, what a path built from that template becomes when the id is empty: it answers HTTP 400 with code `5003` "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", and only `DELETE /v1/orders` without the trailing slash is the cancel-all; before, the venue served `/v1/orders/` as `DELETE /v1/orders`, so such a request cancelled every open order its query matched (with `clOrdId`, at most that order), without one every open order of yours on every market; these refusals are in force at publication by the operator's decision of 2026-10-21, the venue being before production go-live, the 30-day notice of the versioning policy waived for them, as for item (3) of revision 3.73, revision 3.81 and revision 3.82. (6) Unchanged: without a filter both routes cancel every open order you have on every market, untriggered conditionals included; an empty or unknown `symbol` answers `2001` and an empty `clOrdId` `2014`; every request counts one order message on your account-wide order-to-trade ratio as since revision 3.74. There is no `clientUid` filter: the matching engine's cancel-all takes no end-client predicate and the venue offers no FIX mass cancel to carry one; cancel one end client's orders by `orderId` with `POST /v1/orders/batch/cancel`, the ids and their `clientUid` from `GET /v1/orders/open`. Companion documents: the Claude Code REST and WebSocket manuals, the Integration Pack, and the REST, WebSocket and reference pages of the documentation site. Pinned by `CancelAllFilterTests` and `CancelAllFilterDocsTests`. |
| 3.83 | 2026-10-20 | MEMBER QUESTIONS OF OCTOBER 2026: TWO CORRECTIONS ON THE WIRE, THE CLUSTERED SANDBOX AND ITS HOST, AND WHAT THE QUESTIONS SHOWED UNSTATED (sections 1, 1a, 2, 2a, 3, 4, 6, 6b, 7, 9, 10.2, 10.3, 10.5, 10.5a, 10.7, 10.8, 10.9, 11.2, 11.3 and 12, with a dated migration table in 10.7; `/openapi.json` `info.version` 2.39.0 to 2.40.0; complete code reference revision 2.40, the `2003`, `2019`, `3006`, `5000`, `5001` and `5030` rows, the `USER_REQUESTED` value, the `REPAIRING` state and the retry matrix; FIX rules of engagement revision 2.30; Conformance Test Plan revision 1.6; Integration Pack 3.16; Member Technical Handbook 2.20; Contract Specifications, USD-Settled Perpetual Contracts Draft 0.4, USD-Settled Tradfi Perpetuals Draft 0.3 and USD-Settled Knock-Out (Barrier) Contracts Draft 0.4, USD-Settled Spot Markets Draft 0.3 and Fixed Yield Contracts Draft 0.3; the Claude Code REST, WebSocket and FIX manuals, the Postman collection and its guide, the documentation site and the member portal's documentation links). Every request that worked at revision 3.82 works identically EXCEPT the answers of items (1) and (2), corrections in force at publication by the operator's decision of 2026-10-20, the venue being before production go-live, the 30-day notice of the versioning policy waived for them, as for item (3) of revision 3.73, revision 3.81 and revision 3.82. (1) THE WEBSOCKET ACK OF A WRITE WHOSE COMMIT IS NOT CONFIRMED (clustered deployments; section 10.7 and the `5000` row of section 9): a WebSocket write op whose commit the venue did not confirm within its 10 second window acks `5000` with `msg` `internal error`, the code and text REST answers the same case with (HTTP 500); before, it acked `2013` "malformed request", which the retry matrix classes as a request to fix. The outcome of such a write is unknown and it may still execute: read the order back on the leader by `clOrdId`, or send it again with the SAME `clOrdId`, before you decide. Every other ack is unchanged, the two other venue-side faults a single WebSocket write can meet included (complete code reference, the `5000` row). (2) `GET /v1/executions` AND `GET /v1/account/ledger` HELD BELOW A STORE HOLE (sections 4, 6 and 11.3): while the venue's history store has a recorded hole (`store.status` `REPAIRING`), both serve only the rows at or below the store watermark and answer it as `asOfSeq`, with and without a `symbol` or `kind` filter, as the event stream routes and the rejection rows of `GET /v1/orders/history` do; before, they could carry rows from after the hole while the hole's own rows were missing, under an `asOfSeq` that named the watermark. Outside a hole both read exactly as before. The closed-order rows of `GET /v1/orders/history` are not held: until the status is `COMPLETE` again they can include orders closed after the hole, above `asOfSeq`, while orders closed inside it are missing. The dated migration table in section 10.7 gives items (1) and (2) row by row. (3) THE `2019` SENTENCE OF AN OUT-OF-RANGE CURSOR FIELD (section 11.2; `msg` only, code `2019` and HTTP 400 unchanged): a well-formed 30-digit `afterCursor` whose `seq` lies above 9223372036854775807 or whose `eventIndex` lies above 2147483647 answers `afterCursor seq (characters 1 to 19) is out of range: it must be 0 to 9223372036854775807` or `afterCursor eventIndex (characters 20 to 29) is out of range: it must be 0 to 2147483647`, the two joined by `; ` when both fields are; before, it answered `afterCursor must be 30 numeric characters`, which stays the text of anything that is not 30 ASCII digits. Branch on the code. (4) THE GRID OF A LISTED MARKET (section 2a): the venue's listing controls refuse an in-place change of `tickSize`, `qtyStep` or `contractMultiplier` on a listed perpetual as on every other market kind, so a grid change is always a RE-LISTING, and section 2a now states it: the written announcement in advance, the delisting (`STOP_TRADING` with `DELISTING`, every order cancelled `INSTRUMENT_HALT`, `3006` from then on, reduce-only orders included), the final settlement, the new listing whose symbol and `isin` the announcement names (`2001` for the symbol between the removal and the new listing when it keeps the symbol) and the absence of any field or frame that announces a re-listing before the date; `minNotional` and the other Trading Parameters (bands, fees, funding, caps) stay changeable in place. `3006` and the `DELISTING` reason are permanent for the listing, not for the symbol. (5) THE CLUSTERED SANDBOX AND ITS HOST (sections 1 and 1a): the sandbox is `https://sandbox.mtf.perpetuals.net` (`wss://sandbox.mtf.perpetuals.net` for the WebSocket), three nodes behind that one entry host, and replaces the single-node sandbox at `sandbox.mtf.perpetuals.com` of revision 3.24 from the date venue operations announce. A leader change happens there as it can on production, with the `5030`, the `1001` close, the FIX Logout and the `REPAIRING` window of items (7) and (8), and venue operations stage a leader hand-over at an agreed time for Conformance Test Plan case I-05, as for R-06 and R-07. (6) PRODUCTION LISTS THE KNOCK-OUT MARKETS (sections 1a and 2a): section 1a called the `*-USD-KO` markets sandbox-only for now; production lists the perpetual and the knock-out markets. Section 1a, which announced two deliberate differences and listed three, now lists the two, states that the production listing as planned today uses the sandbox's grid and `minNotional` on every market both list, and section 2a marks its table as the sandbox listing and names what production lists as planned today: the thirteen perpetuals listed before 30 September 2026, the knock-out markets of BTC, ETH, SOL, BNB, XRP and JPY and the six swap markets, no spot market. Two sentences of section 2a, since revision 3.71, had production list every knock-out market of the table, the nine fractional ones from its first day; they now name its knock-out markets. The production instrument list is confirmed with the go-live listing. (7) THE LEADER CHANGE, STATED (section 10.7; the `5000`, `5001` and `5030` rows of section 9; documentation only): `leader` is an `https` base URL, on every venue deployment the environment's published entry host; accept it only when it equals the entry host you configured, otherwise send no credentials there, reconnect to your entry host and tell venue operations; while no leader is known REST answers `leaderId` 0 with `leader` null and the WebSocket ack `leaderId` 0 and no `leader`. A node that is not the leader answers `5030` to every request that is not a `GET` under `/v1` (`POST /v1/margin/preview` included), never forwards it, first reads the rest of the body for at most 2 seconds over HTTP/2, and closes every HTTP connection after its answer, reads included, but does not close a WebSocket opened on it: close it yourself at the first `5030`. A `5030` is a redirect, never a rejection: a cancel, cancel-all, amendment or cancellation batch answered `5030` did not take effect and is sent again on the leader at once, and a `session.cancelOnDisconnect` answered `5030` is not armed. A `5001` on a single write at a leader change, a `5000` (item (1)), a batch row `5000` and a missing ack after the `1001` close do not prove that the command was not executed: reconcile by `clOrdId` on the leader before resubmitting. On FIX a write whose commit is not confirmed within 10 seconds ends the session without a Logout and without a report, a close that counts as a disconnect for an armed cancel on disconnect (FIX rules of engagement revision 2.30, section 1): reconcile by `ClOrdID(11)` on the leader. The restart guarantee "executed means acked" names the leader change as its exception. (8) `REPAIRING`, STATED (sections 4, 10.8 and 11.3; documentation only): the store block describes the node that answered; `REPAIRING` starts at every leader change of a clustered venue, when the store falls further behind than its write buffer absorbs and when a store write fails beyond a short retry; no timeout ends it and the venue sets no maximum duration; it ends when the hole is re-folded from the journal, by the new leader after a leader change and by venue operations otherwise, a hole that opens on a running leader included; `lagSeq` counts from the start of the hole to the engine head; trading and the live feeds are unaffected; a node that is not the leader answers the event stream reads from its own store position, while its `GET /v1/executions` and `GET /v1/account/ledger` without a recorded hole read the shared store whole and can run ahead of the `asOfSeq` it answers. The documents said that the venue was repairing every hole. (9) THE CURSOR, STATED (sections 11.2 and 11.3; documentation only): `seq` is a signed 64-bit integer, 0 to 9223372036854775807, `eventIndex` a signed 32-bit integer, 0 to 2147483647, the 0-based position of an event among the events of one engine command that restarts at 0 for every `seq`, and `leg` one digit. The last-page position of item (3) of revision 3.73 is served on an environment from the date venue operations announce for it there, a condition that row and its migration table left out; before that date, and on a page read while the store recorded or cleared a hole, a last page answers the rule before revision 3.73. (10) ORDERS, STATED (sections 6, 10.2, 10.3, 10.5 and 10.5a; documentation only): `acceptedTs` per surface (the time of the reported event on every order frame, every stream row and every answer rendered from an order event; the placement time, moved only by a cancel-and-new amendment, on the open-order reads; the first acceptance on the `FILLED`, `CANCELED` and `EXPIRED` history rows and the rejection time on a `REJECTED` one); a successful single `order.amend` or `order.cancel` ack carries no `data`, and where the applied state is read instead; the `DELETE /v1/orders/{orderId}` answer is the cancelled order; the `PUT /v1/orders/{orderId}` answer is read after the amendment under the amendment's own `asOfSeq`; the reduce-only trim also reaches untriggered conditional orders, newest first; the `order` channel reports no partial fill in a frame of its own and never reads `PARTIALLY_FILLED` (the sentence of revision 3.60 that the `order` channel and `GET /v1/orders/history` derived it is corrected: of the event payloads only the FIX fill report carries the partial state); a cancel on disconnect reports `USER_REQUESTED` by design, like a member-wide cancel-all of your own and the member portal's kill switch, and the four facts that tell it apart; `status.{symbol}` also repeats an unchanged status when the venue edits the market's configuration. (11) CORRECTIONS OF EARLIER ROWS: the instrument ids item (47) of revision 3.70 and item (3) of revision 3.71 name are internal and no member surface serves an instrument id, so key on the symbol; the announcement of a re-listing names the symbol and the `isin` of the new listing (section 2a); the sentence of revision 3.32 that you compute each client's funding charge by multiplying `fundingPerUnit` by position size is corrected by item (15). (12) `/openapi.json` 2.40.0: the 503 of `POST /v1/orders`, `DELETE /v1/orders`, `PUT /v1/orders/{orderId}` and `DELETE /v1/orders/{orderId}` (`5001`, and `5030` on a node that is not the leader) documented beside the batch routes'; `StreamEvent.eventIndex` an `int32` from 0 to 2147483647; `afterCursor` the pattern of 30 ASCII digits; `acceptedTs` and `closedTs` described, and the `from` and `to` parameters of `GET /v1/orders/history` naming the `acceptedTs` they bound; the 200 answers of `PUT /v1/orders/{orderId}` (the order read just after the amendment) and `DELETE /v1/orders/{orderId}` (the cancelled order rendered from the cancel event) described; the `StreamStore.status` text, the `StreamStore.lagSeq` description (counted from the start of the hole while `REPAIRING`) and the `nextCursor` condition of item (9); the hold of item (2) on `GET /v1/executions`. (13) THE COMPANION DOCUMENTS: the Claude Code REST, WebSocket and FIX manuals, the Integration Pack, the Member Technical Handbook, the Postman collection (its `baseUrl`) and guide and the pages of the documentation site name the new sandbox host of item (5) and state items (1) to (10) where they describe the same surface; the REST manual's reference-data cache "refreshed on venue-events" is corrected (`venueEvents` carries trading-state changes only, so refresh after an announced date and on an unexpected `2001`, `2002`, `3006` or `DELISTED`); the site's REST page lists the complete `cancelReason` set, `POST_ONLY_WOULD_CROSS` included; the contract specifications state that a change of the Tick Size or the Quantity Step of a listed market is made only by a re-listing announced in advance (Perpetuals Draft 0.4 and Tradfi Perpetuals Draft 0.3, sections 7.1, 7.3 and 14.1; Barriers Draft 0.4, sections 7.1, 7.2, 7.5 and 15.1; Spot Markets Draft 0.3, sections 6.3, 6.5 and 12.1; Fixed Yield Contracts Draft 0.3, sections 6.2, 6.4 and 14.1, for the Quantity Step), the Barriers specification's section 2.2 states the production listing of item (6) where it called it pending, and its section 10.4 states that a Request open at the removal of a market can no longer be quoted or accepted and that a close Request ends with its Contract, where it said every open Request was cancelled; the member portal's documentation links name the new sandbox host; the Conformance Test Plan's case B-08 names the condition of item (9). (14) CONDITIONAL ORDERS: NO BAND AND NO COLLAR, STATED (section 6 and the `2003` row of section 9; documentation only, the exemption kept by the operator's decision of 2026-10-20): neither the trigger `price` nor the `limitPrice` of a conditional order is checked against the price band, at placement, on amend or at activation, so a `STOP_LIMIT` or `TAKE_PROFIT_LIMIT` is accepted, and its triggered limit child rests and trades, at prices a direct `LIMIT` order is refused `2003` at; and a triggered limit child is not collared either: the protection collar applies to `MARKET` orders and the market children only, so a limit child trades through the book up to its `limitPrice`. The `2003` row said "checked at placement" without the exemption, and the Member Technical Handbook (its `priceBandBps` row, 3.1 and 3.3) implied a band on every resting limit price; the complete code reference's `2003` row, the Handbook and the site's trading and reference pages state the exemption now. (15) FUNDING, REPRODUCED (section 7; documentation only): every booked funding charge is reproducible exactly from the settlement's `fundingRate` and `markPrice`: `netQty x markPrice x contractMultiplier x fundingRate` rounded half-even to the micro, on the member net position and on each client sub-position, the member charge minus the sum of the client charges added to the sub-position of the largest absolute `netQty`; nothing is booked, on the member or on any client sub-position, while the member net position is 0 at the settlement (on a LOCAL_DAILY market at the snapshot), whatever the sub-positions hold; the remainder can reach about half a micro per client sub-position, and the per-client `accFunding` sums exactly to the member's over every sub-position that ever held on the market, where `GET /v1/positions/end-clients` lists the open ones only; on a LOCAL_DAILY market the settle mark is the mean rounded half-even to the tick and the rate comes from the unrounded mean. `fundingPerUnit` and `predictedFundingPerUnit` are display figures, the charge of one `qtyStep` rounded half-even to the micro and divided by the `qtyStep`, so a quantity times `fundingPerUnit` can differ from the booked charge by up to half a micro per `qtyStep` held, and the figure can read `0` while a charge is booked; on `GET /v1/funding/history` it is computed at the read with the market's listing at that moment. `/openapi.json` 2.40.0 describes the two fields and the history route so, and the site's REST page and the Member Technical Handbook state the rule. (16) THE QUICK TABLE OF SECTION 9, COMPLETED: the rows of `1005` and of `2039` to `2045`, which the complete code reference carries, are added, and the html edition of this document gains the `2049` row of revision 3.70 that only the Markdown edition carried, so section 9 lists every code of the registry a member can receive. (17) `POST /v1/rfq` IS NOT IDEMPOTENT ON `clOrdId`, STATED (sections 2 and 6b; documentation only, by design): RFQ creation, a swap offer included, echoes `clOrdId` and never deduplicates on it, so a resend with the same `clOrdId`, identical or not, opens another request and never answers `2009`, and order entry does not see an RFQ's reference; reconcile with `GET /v1/rfq/mine` and withdraw a duplicate with the free cancel. `/openapi.json` 2.40.0 `RfqCreateRequest.clOrdId`, which said "same rules as order entry", says so. (18) THE FEE BLOCK OF `GET /v1/account/parameters`, STATED (section 7; documentation only): `fees` is never null; its `measuredAt`, `volume30d` and `cycle` read null until the first measured fee cycle that includes your account, and so do `tier` and `criteria` unless you are enrolled on a Market Maker Scheme tier, which they then name with its obligations; on a venue started after the last cycle that is every member not on the scheme until the next one, unless venue operations run a measurement before it, and the fee deducted on each market is on every `markets[]` row. `/openapi.json` 2.40.0 describes the block so, and the site's REST page states it. Pinned by `WsApiTests.PrivateWs_AWriteWhoseCommitIsNotConfirmed_Acks5000LikeRest_Never2013`, `HistoryReadsBelowAHoleTests`, `HistoryHeldTextTests`, `StreamCursorUnitTests`, `KoRelistingGuardTests`, `AddInstrumentsScriptTests` and `MemberQuestionsDocsTests`. |
| 3.82 | 2026-10-19 | FIX: EVERY FILL REPORT STATES THE ORDER AFTER THAT FILL (FIX only: FIX rules of engagement revision 2.29, section 4, its paragraph "Quantities and status on a report", and conformance checklist items 4 and 25; no REST or WebSocket change: no field, HTTP status, enum, code, default or example value is added, removed or renamed on either, `/openapi.json` `info.version` stays 2.39.0 and the complete code reference stays at revision 2.39). Every request that worked at revision 3.81 works identically and every FIX message keeps its tags; what changes is the VALUE of `OrdStatus(39)`, `LeavesQty(151)` and `CumQty(14)` on some ExecutionReports, corrections of values that contradicted the published rules (section 4 of the rules of engagement: exactly one lifecycle report closes an order) and of the `CumQty(14)` per session that the documentation site described (item (2)), in force at publication by the operator's decision of 2026-10-19, the venue being before production go-live, the 30-day notice of the versioning policy waived for them, as for item (3) of revision 3.73 and revision 3.81. (1) A fill report (`150=F`) states the order after THAT fill: `CumQty(14)` is its filled quantity including this fill and no later one, `LeavesQty(151)` what this fill left open, and `OrdStatus(39)` is `1` while anything is open and `2` only on the fill that completes the order; on a trade between two of your own end clients each of the two fill reports carries its own order's values. Before, a fill report carried the order's state after the whole command that produced it, not after the fill, for an order that partly filled and was then canceled within the same command (an IOC or market remainder, a self-trade prevention cancel of the aggressor, an unfunded spot or position-size cancel, a reduce-only trim, a liquidation slice) and for an order that swept several resting orders: the canceled order received `150=F` with `39=2` and `151=0` and then its cancel `150=4`, two reports with a terminal `39` for one order, and every fill of a sweep carried what the whole command left open, `39=2` and `151=0` on every fill of a sweep the command completed or canceled, every fill after the first then with `CumQty(14)` equal to its own `LastQty(32)`, and `39=1` with the final `LeavesQty(151)` on every fill of a sweep whose rest stayed on the book. (2) `CumQty(14)` is the order's own on every report, the fill, the cancel (`150=4`), the expiry (`150=C`), the replace (`150=5`) and the rejection (`150=8`) of an order the venue had accepted (a post-only order repriced to cross): it counts every fill of the order, whichever transport placed it and whenever the session logged on, so it is the same on every FIX session of your member and agrees with the `150=I` restatement. Before, each session summed only the fills it had itself been sent since its first report of the order: an IOC remainder cancel carried `14=0` after its fill, and a session that logged on, or logged on again, after a fill of an order left that fill out; and a rejection always carried `14=0`, so a post-only order partly filled and then repriced by a replace (`35=G`) to cross received `150=5` with its `CumQty(14)` and then `150=8` with `14=0`. (3) Unchanged: a fill with `OrderID(37)` `0` (an RFQ or barrier fill, a yield contract take, an ADL close, an operator's trade reversal) is complete in itself, `39=2`, `151=0` and `CumQty(14)` equal to `LastQty(32)`; the `39` of a `150=I` restatement stays the order's `status` as `GET /v1/orders/{orderId}` reports it. The documentation site's advice to rebuild `CumQty` as `OrderQty` minus `LeavesQty` after a reconnect is withdrawn, because it books the canceled remainder of an IOC as filled: take an order's cumulative quantity from `CumQty(14)`, or sum `LastQty(32)` over its fills. Companion documents: the Claude Code FIX manual and the FIX page of the documentation site. Pinned by `FixPerFillQuantityTests`, `FixOrderEntrySurfaceTests`, `TradeOrderQuantityTests`, `AuctionTests` and `FixPerFillDocsTests`. |
| 3.81 | 2026-10-19 | THE END-OF-DAY RECONCILIATION SNAPSHOT (section 7, with a dated migration table after its table; `/openapi.json` `info.version` 2.38.0 to 2.39.0; the complete code reference stays at revision 2.39 and the FIX rules of engagement at 2.28; Integration Pack 3.15; Conformance Test Plan 1.5). Every request that worked at revision 3.80 works identically EXCEPT that the CSV form of `GET /v1/reconciliation/end-client-positions` prints `date` as `YYYY-MM-DD` and quotes per RFC 4180 (items (2) and (4)) and that `GET /v1/executions` omits an empty `positionEffect` (item (6)); these three are corrections in force at publication by the operator's decision of 2026-10-19, the venue being before production go-live, the 30-day notice of the versioning policy waived for them, as for item (3) of revision 3.73. (1) THE ROUTE DESCRIBED: `GET /v1/reconciliation/end-client-positions?date=&format=`, served since the first release and until now only named in section 4, has its row in section 7 and is documented in `/openapi.json` (operation `getEndClientPositionsEod`, schema `EodClientPosition`): the venue's daily anchor takes the snapshot within about five minutes after 00:00 UTC for the day that just ended, on a venue that keeps trading, so a fill of the first minutes of the new day can be in it (the Integration Pack and the WebSocket manual called it a venue-quiescent anchor); if the venue restarts, or another of its nodes takes over, before the next anchor, the anchor runs again for the previous day and adds the rows of the sub-positions that day does not hold yet, from the state at that moment (all of the day's rows when the anchor had not run at midnight), as it always did, and a snapshot venue operations take by hand does the same; rows are immutable once written. (2) THE CSV DATE: the `date` column of the CSV is `YYYY-MM-DD`, as the JSON form and the `date` parameter always were; before, every data line began with the month-first `MM/DD/YYYY` (`10/18/2026` for 18 October 2026) on every venue host since the first release, so a parser that read that text must now read the ISO date. The `date` input the route accepts is unchanged. (3) `upnl` FILLED: the CSV column, published and empty on every row until now, and the new JSON member `upnl` (additive, omitted when empty) carry the unrealized PnL at the snapshot's `mark` in the market's settlement currency, exact to the micro: the `unrealizedPnl` `GET /v1/positions/end-clients` reports at that mark, from the booked cost basis, so it can differ by micros from `netQty` x (`mark` - `avgEntry`). It is written only where the history store's net quantity for the row equalled the matching engine's at the snapshot's instant, and is empty otherwise, without a mark, and on every row recorded before this revision (rows are never rewritten). `netQty` and `avgEntry` come from the history store exactly as before. (4) RFC 4180 QUOTING: a CSV field that holds a comma, a double quote, CR or LF is wrapped in double quotes with every inner quote doubled (before, a `clientUid` holding a comma split its line); nothing else is escaped. (5) `/openapi.json` CORRECTIONS (no wire change): the lot texts carry the corrections of revision 3.80, section 6e (the dated 2.10.0 note and `Lot` said per-lot `unrealizedPnl` sums exactly to the sub-position's; `LotClosure` and `GET /v1/lots/closures` said the two views can never disagree; the closures route said an IOC that found no liquidity degrades to FIFO), and `Execution` documents `positionEffect`, served since revision 3.32. (6) `positionEffect` ON `GET /v1/executions`: a fill that left your client's sub-position unchanged carries no `positionEffect`, as on the WebSocket execution frame and the event stream; before, the route served it as the empty string `""`. |
| 3.80 | 2026-10-18 | THE LOT VIEW CORRECTIONS (section 6e and section 7 "Catch-up fence"; FIX rules of engagement 2.28; no field, endpoint, HTTP status, enum, code, default or example value is added, removed or renamed, and `/openapi.json` `info.version` stays 2.38.0). Two corrections of the fold: a book liquidation of a hedge-mode client is folded into its own `LONG` or `SHORT` bucket, so `GET /v1/lots/closures` lists it against that bucket's lot (before, the fill was folded into the netting bucket: the closures feed did not list it against the bucket's lot, and `GET /v1/lots` reached the right quantity only through the clamp); and consecutive FIX `9028` partial closes of an order that filled in several prints walk that order's lots oldest first (before, a second partial close could send part of its quantity FIFO to the client's oldest lot). Corrected statements: the open lots' `qty` sums exactly to the bucket, their `unrealizedPnl` does not once a partial close leaves lots at different entry prices (the worked example in section 6e); the closures feed folds without the clamp, so the two views can differ by a close the fold cannot attribute; the funding shares are exact against the charge on the bucket net, and the booked figure of the client that receives the member-level rounding remainder can differ from its lots by that remainder; an IOC that finds no liquidity closes nothing; the retry outcomes of `POST /v1/lots/{lotId}/close` after a lost response; `3014` refuses every order without `reduceOnly`, a client-level close included. |
| 3.79 | 2026-10-17 | REPEATED ENGINE FAULTS ANSWER `1003` (section 4; complete code reference revision 2.39, the `1003` and `5005` rows and the retry matrix; no field, HTTP status, enum, code, default or example value is added, removed or renamed, and `/openapi.json` `info.version` stays 2.38.0). When ten of your commands answer `5005` within 60 seconds, every request of yours that consumes the order budget answers HTTP 429 with code `1003` and `retryAfterMs` until the oldest of the ten is 60 seconds old (up to 60,000 ms), on REST, WebSocket and FIX exactly as a rate-limit refusal does (REST `Retry-After`, the WebSocket ack's `data.retryAfterMs`, FIX `Text(58)`); the refused request does nothing and consumes no order-budget unit. Such a refusal names its cause: `msg` `rate limit: repeated engine faults (5005)` on REST and in the WebSocket ack, and FIX `Text(58)` begins with the same words before the wait; every other `1003` keeps its text, and both begin with `rate limit`, so a client that matches that prefix reads both as a throttle. 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 the budget too and still consumes its order-budget unit: a reduce-only order (`reduceOnly` true on REST and the WebSocket `order.place`, FIX `9001=Y` on `35=D`), a placement batch (`POST /v1/orders/batch`, WebSocket `orders.batch`) whose every entry is reduce-only, a lot close (`POST /v1/lots/{lotId}/close`) sent with `reduceOnly` true, and a barrier early close (the holder's close request, `POST /v1/rfq` with `closeContractId`, the writer's quote on that close request and the holder's accept of that quote; each passes only for your own contract, any other answers `1003`, 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, they answer `5001` as below the budget while order-budget units remain); an amendment, single or batch, is still refused, and so is a whole placement batch with one entry that is not reduce-only. When the budget trips, the venue mails your trading contact on file (without one, your incident or compliance contact) once. Ten `5005` answers in 60 seconds is a published rule of the venue: production runs exactly these values on every node. While your commands do not answer `5005`, every request that worked at revision 3.78 works identically. This revision also corrects the description of close `1001` `venue restart` in section 10.7 and of "Restarts and reconnection" there (no wire change): besides a venue stop for a rollout or restart, the venue sends the same advisory and close, and FIX the same `Logout(35=5)` with `Text(58)="venue restart"` (FIX rules of engagement revision 2.27, section 1), 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; recover the same way: 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 (section 10.8); the venue adds no other close code or reason for it. In force on every environment from the date venue operations announce. Pinned by `CommandFaultBudgetWireTests`, `ProductionPostureGuardTests` and `DocsDownloadsSyncTests`. |
| 3.78 | 2026-10-17 | THE FIX DROP COPY REPORTS A CONDITIONAL ORDER'S ACTIVATION, AND A HEARTBEAT TIMEOUT SAYS WHY (FIX only: FIX rules of engagement revision 2.26, sections 1 and 4 and conformance checklist item 24, and section 6 of this reference; no REST or WebSocket change: no field, HTTP status, enum, default or example value is added, removed or renamed on either, `/openapi.json` `info.version` stays 2.38.0 and the complete code reference stays at revision 2.38). Every request that worked at revision 3.77 works identically. (1) When the trigger of a stop or take-profit order fires, every FIX session of the member now receives an ExecutionReport with `ExecType(150)=L` ("Triggered or Activated by System") and `OrdStatus(39)=0`, carrying `OrderID(37)`, `ClOrdID(11)` when the order has one, `Symbol(55)`, `Side(54)`, `LeavesQty(151)`, `CumQty(14)` `0`, `TransactTime(60)` and the trigger price in `StopPx(99)`, and neither `Account(1)` nor `9016` (the order's accept carries both). Before, FIX sent nothing between a conditional's accept and its first fill, while the WebSocket `order` channel (`OrderTriggered`) and the event stream (`ORDER_TRIGGERED`) carried the activation and the rules of engagement promise a drop copy of all order activity. A FIX client that refuses an unknown `ExecType` must accept `L` and move the order from pending trigger to working on it, never book it as a fill. (2) A FIX session silent past twice its `HeartBtInt` now receives `Logout(35=5)` with `Text(58)` "heartbeat timeout" before the venue disconnects; before, the frame raced the close and the connection ended with no frame. The pre-logon deadline's "logon timeout" Logout takes the same path. (3) Nothing follows a Logout the venue sends: from that frame to the disconnect the session acts on no message it receives (a Logon that crosses the "logon timeout" Logout is not answered and disarms no pending cancel-on-disconnect) and receives no further `ExecutionReport(35=8)`; reports of that window reach your other sessions and the event stream as before, so recover them through `GET /v1/stream/events`. |
| 3.77 | 2026-10-16 | LEADER CHANGE ON FIX (clustered deployments only, a single-node deployment is unchanged; FIX rules of engagement revision 2.25; no REST, WebSocket or schema change: no field, HTTP status, enum, default or example value is added, removed or renamed, `/openapi.json` `info.version` stays 2.38.0 and the complete code reference stays at revision 2.38). Every request that worked at revision 3.76 works identically. (1) A NewOrderSingle, OrderCancelRequest, OrderCancelReplaceRequest or MarketDataRequest received by a node that is not the leader is answered with `Logout(35=5)`, `Text(58)` "leader change, reconnect", then the close; reconcile by `ClOrdID(11)` on the leader before resubmitting; reconnect to the leader and recover through the event stream, as section 10.7 describes for a leader change. Before, such a session was answered `9010=5001` "engine busy, retry" (or a `35=9` "engine busy") for as long as the node's demotion was delayed. (2) `9010=5001` "engine busy, retry" and the `35=9` "engine busy" reject now mean a full intake on the leader only; an order appended just before a leader change may still execute: reconcile by `ClOrdID(11)` on the leader before resubmitting. (3) A session the leader change logged out arms no cancel-on-disconnect, like a restart; resting orders survive on the new leader. (4) A FIX session receives the Logout on every leader change, also on a node that regains leadership at once. |
| 3.76 | 2026-10-12 | LEADER CHANGE ON THE WEBSOCKET (sections 6 and 10.7; clustered deployments only, a single-node deployment is unchanged; no REST, FIX or schema change: no field, HTTP status, enum, default or example value is added, removed or renamed, `/openapi.json` `info.version` stays 2.38.0, and the complete code reference moves to revision 2.38 with the `5030` row and the `1001` close code row grown). Every request that worked at revision 3.75 works identically. (1) A node that loses leadership while it stays up closes every WebSocket the way a restart does, with the advisory `leaderChange` and close `1001` reason string `leader change, reconnect` (the FIX Logout's words); before, the sockets stayed open on a node that no longer accepted writes. A leader change voids cancel-on-disconnect arming like a restart. (2) Every WebSocket write op on a node that is not the leader acks `5030` `NOT_LEADER` with `leaderId` and `leader` under `data`, before anything is charged or counted, as the batch amendment and cancellation ops have since revision 3.74, and first, ahead of authentication and validation, as the REST refusal is (`marketSegments.set` with a member code unknown to that node acks `5030`, not `5002`); `session.cancelOnDisconnect` is a write op too. Before, `order.place`, `order.cancel`, `order.cancelAll`, `order.amend` and `marketSegments.set` acked `5001` "busy", `session.cancelOnDisconnect` acked `armed` although that node could not submit the cancel, and `orders.batch` answered `5001` rows after the charge. |
| 3.75 | 2026-09-30 | THE DEV MARKET SET OF 30 SEPTEMBER 2026: NINE NEW UNDERLYINGS EACH WITH ITS KNOCK-OUT MARKET, ONE OF THEM A USD-SETTLED USD/JPY QUANTO (sections 1a, 2a, 6b, 6d, 6f and 7; a LISTING revision on the operator's decisions of 30 September 2026, decision ADR-30, the register entry pending; no wire schema change: no field, HTTP status, enum, default or example value is added, removed or renamed, no request changes, `/openapi.json` `info.version` stays 2.38.0 with ONE description string reworded in place, the commodity set of `positionHolderCategory` naming XAG, and the complete code reference stays at revision 2.37). Every request that worked at revision 3.74 works identically. (1) THE LISTINGS. The sandbox lists eighteen new markets, each perpetual with its knock-out market: `HYPE-USD-PERP` and `HYPE-USD-KO` (tick `0.01`, `qtyStep` `0.01`), `DOGE-USD-PERP` and `DOGE-USD-KO` (tick `0.00001`, `qtyStep` `1`), `ZEC-USD-PERP` and `ZEC-USD-KO` (tick `0.05`, `qtyStep` `0.001`), `NEAR-USD-PERP` and `NEAR-USD-KO` (tick `0.0001`, `qtyStep` `1`), `TAO-USD-PERP` and `TAO-USD-KO` (tick `0.01`, `qtyStep` `0.01`), `US500-USD-PERP`, `US500-USD-KO`, `US100-USD-PERP` and `US100-USD-KO` (US equity indices, tick `0.1`, `qtyStep` `0.001`), `XAG-USD-PERP` and `XAG-USD-KO` (silver, tick `0.001`, `qtyStep` `0.01`) and `USDJPY-USD-PERP` and `USDJPY-USD-KO` (tick `0.001`, `qtyStep` `0.01`), with `minNotional` 5 USD on the perpetuals and 100 USD on the knock-out markets, the 10M USD order and 2B USD position notional caps of every perpetual listing, the 10M USD order cap and no position notional cap on the knock-out markets, the knock-out economics of every listed knock-out market (`knockOutRebateBps` 1000, `strictTakeProfitPremiumBps` 5000, `maxContractLifeHours` 8784) and `barrierGuard.entryBandBps` 30 on US500-USD-KO and US100-USD-KO and 20 on USDJPY-USD-KO (50 on the other six). Funding: the 8 h UTC GRID on the five crypto perpetuals (rate source TICK, like the other crypto perpetuals until the date of section 1a), the daily local settlement of section 7 on the four others (US500 and US100 New York 16:00, XAG London 16:00 with gold, USDJPY London 16:00 with the currencies). The listing values in force are the ones on `GET /v1/instruments`. (2) THE USD/JPY QUANTO. USDJPY-USD-PERP and USDJPY-USD-KO are priced on the USD/JPY rate in yen per dollar (about 157) and one unit pays 1 USD for each full 1.000 move of the rate, never converted at the live rate (section 2a); `baseAsset` reads `USDJPY`. JPY-USD-PERP and JPY-USD-KO, the inverse on 1,000 JPY units, are unchanged beside them. (3) WHAT STAYS. SP500-USD-PERP, NDX100-USD-PERP, BNB-USD-PERP, BRENT-USD-PERP and JPY-USD-PERP and their knock-out markets stay listed and active, their rows unchanged; no market is renamed, delisted or re-sized, and no existing listing value changes. (4) REGULATORY CLASSES. XAG-USD-PERP and XAG-USD-KO are treated as commodity derivatives on the reading of revision 3.70, which the venue applies to silver pending its compliance function's confirmation (section 6d: the Article 58(4) category applies to a client that holds a position there); the K-TCD classes of section 6f name the new markets. (5) ACTIVATION. On the sandbox from the date venue operations announce (the node restart that lists the rows); production lists none of them. (6) COMPANION DOCUMENTS, documentation only: the Contract Specifications, USD-Settled Perpetual Contracts Draft 0.3 (section 2.2 and the quanto's carve-out in sections 4.1, 4.2 and 6.2), USD-Settled Tradfi Perpetuals Draft 0.2 (the four new rows with the neutral index descriptions, the quanto terms of sections 4.1, 4.2, 4.4, 6.5 and 12.2 and the funding rows of section 9.3) and USD-Settled Knock-Out (Barrier) Contracts Draft 0.3 (the nine rows of sections 2.2 and 7.2, the quanto's carve-out in sections 2.1, 4.3, 4.4 and 6.2 and the class band of section 7.3), the DRAFT banners staying until the Head of Legal's approval is recorded; the Member Technical Handbook to revision 2.18 (section 2), the Integration Pack to revision 3.12 (the eighteen table rows); the trading and REST pages of the documentation site. Pinned by `VenueConfigTests` (every value of the eighteen rows and their feed lines), `BarrierQuoteGuardWireTests` (the twenty-two knock-out rows on every surface) and `DocsDownloadsSyncTests` (this row, its html transcription and the listing on every surface). |
| 3.74 | 2026-09-29 | BATCH AMENDMENT AND BATCH CANCELLATION (sections 4, 6, 6c, 9 and 10.5; `/openapi.json` `info.version` 2.37.0 to 2.38.0; complete code reference revision 2.37, the `1003`, `1004`, `2002`, `2013`, `2014`, `2017`, `5000`, `5001`, `5005` and `5030` rows). ADDED: two routes and two WebSocket ops; no field, HTTP status, enum, default or example value is removed or renamed and no code is added. CORRECTED on existing routes, each named below with its basis: the placement batch's per-entry fault rows of item (3), the documentation of item (4), the account-wide count of item (5) and the schema of item (6); every other request that worked at revision 3.73 works identically. The matching engine, its journal and everything it decides are untouched: every entry the venue submits is the ordinary amend or cancel. (1) NEW `POST /v1/orders/batch/amend` and the WebSocket op `orders.batchAmend` (section 6c): `{"amends": [...]}`, 1 to 50 entries `{orderId, price?, qty?}`. Each entry the venue submits is processed by the matching engine exactly as `PUT /v1/orders/{orderId}` (`qty` is the new total quantity; a quantity decrease at the same price keeps queue priority, any price change or quantity increase is cancel+new; every engine check of the single amend applies at the current state, also to an order that already stands at the requested terms). Entry member names are case-sensitive and a value of the wrong JSON type answers `2017` "malformed request body", as in the entries of `POST /v1/orders/batch`. An order named by two entries: the first runs, every later one answers `2013` "order N is already named by entry k of this batch" (k the earlier entry's index, counted from 0). Non atomic: entries reach the matching engine in array order, other members' traffic may interleave, and a refused entry never stops a later one, so send an entry that depends on another's outcome in a second message. `data.results` in array order: the order's state as of the response's `asOfSeq` (the single amend's `data`; a success row can read `REJECTED` or `CANCELED`, item (4), and that level is then gone; when the venue could not read the orders back after the batch, the order's last state change among the batch's entries, every row then the order as of `asOfSeq` or later), or `{code, msg, orderId}` with `orderId` echoed when the entry carried a positive integer. One order-budget unit per entry, charged up front, all or nothing, after the engine-busy check: `1003` and an engine-busy `5001` consume nothing, and the units of a batch whose entries are refused afterwards stay spent. Order-to-trade: an entry naming an order on a market where the regime blocks you answers a `1004` row and the other entries run; an entry counts as one order message on its order's market when the venue hands it to the matching engine. (2) NEW `POST /v1/orders/batch/cancel` and the WebSocket op `orders.batchCancel` (section 6c): `{"cancels": [...]}`, 1 to 50 entries, each `{orderId}` or `{clOrdId}`, exactly one. The venue first looks every entry up: an entry naming no open order of yours (not found, not yours, terminal, or a reference naming no open order) answers `2014` and nothing is submitted for it; every other entry is processed by the matching engine exactly as `DELETE /v1/orders/{orderId}`. Free: no order-budget unit and never blocked by the order-to-trade regime (each submitted entry counts one order message on your account-wide ratio and each cancelled order one on its market, as a single cancel does). One cancellation batch of yours is processed at a time on a venue node and a later one waits for it; `5001` when the venue's intake is nearly full or eight of your cancellation batches are already waiting (your single cancels and cancel-all are never held by it). `data.results`: the cancelled order (the single cancel's `data`), or `{code, msg, orderId, clOrdId}` echoing what the entry carried. (3) BOTH ROUTES: HTTP 200 with envelope `code` 0 even when every entry failed. Envelope errors come only before anything is submitted: authentication `1001`, the read-only credential `1005`, a malformed body or an entry count outside 1 to 50 (`2002`), `5001` when the venue sheds or cannot look the orders up, `5030` with the leader on a clustered deployment's follower node (the WebSocket ops now answer it as REST does), and for the amendment batch the licensing profile `2034` (production) and `1003`. Per entry: an amend or cancel that TOOK EFFECT before a contained fault answers the order's row with `rejectCode` `5005`: reconcile it and never resend it unchanged (the WebSocket and FIX single amend and cancel treat such a command as taking effect too, without a `rejectCode` on the WebSocket ack); an entry the venue did not accept answers `5001`, and after an entry the venue's intake refused as it was submitted every later entry answers `5001` "not submitted" (on a clustered deployment the replicated intake can refuse an entry after the later ones were submitted, and they may take effect: read every row); an entry whose outcome the venue could not confirm answers `5000` "outcome unknown": read the order back on the leader before sending it again. The WebSocket acks of the two new ops carry `asOfSeq`. CORRECTION of the placement batch (`POST /v1/orders/batch`, `orders.batch`): a fault on one entry's processing used to answer the whole request with an undocumented HTTP `500` code `5000` (REST) or `2013` "malformed request" (WebSocket); it now answers that entry's `5000` row and its neighbours' own rows, an entry appended to the log before a loss of leadership that used to answer `5001` now answers `5000`, and after an entry the intake refused as it was submitted every later entry answers `5001` "not submitted". Reconcile a `5000` placement row by its `clOrdId` and resend it only with the SAME `clOrdId`: an identical payload returns the original order. Batch amendment and cancellation are not offered over FIX. The venue may withdraw the two new routes during an incident; they then answer as before this revision (`404`, and `2013` "unknown op" on WebSocket), and the single routes stay. (4) CORRECTION, documentation only (section 6, Manage orders): outside a call auction, an amend that reprices an order to cross the book re-matches it as a new order: a post-only order is rejected `2005` and removed, exactly like a post-only order that would cross at entry; an order that meets your own resting order with the same `clientUid` follows your self-trade policy, which under `REJECT_TAKER` rejects the amended order `2015` (or cancels it `STP_CANCEL` when it was part filled), under `CANCEL_TAKER` and `CANCEL_BOTH` cancels it `STP_CANCEL`, and under the default `CANCEL_RESTING` cancels your resting order instead; your own order with a different `clientUid` trades under the default end-client scope. During a call auction, or while the market is closed, the repriced order rests for the uncross. `PUT /v1/orders/{orderId}` answers these inside HTTP 200 with the order's `status` `REJECTED` or `CANCELED`, so does the batch row, and on the WebSocket the `order.amend` ack reports the amend accepted while the `order` channel carries the event. Every other refusal of an amend leaves the order untouched, as before. (5) ACCOUNT-WIDE COUNT, an alignment with section 4, which defines the account-wide ratio over every market's messages, a cancel you asked for included: `DELETE /v1/orders?clOrdId=` and the cancel-all `DELETE /v1/orders?symbol=&side=` now count one order message on your account-wide ratio (`account.otr` on `GET /v1/account/parameters`), as every other cancel already does; the account-wide ratio is a warning flag to venue operations and never blocks. (6) CORRECTION of the schema, documentation only: `Order.rejectCode` also carries `5005` on an order a contained command left standing (the place ack and the batch rows); `BatchEntryError` also carries the matching engine's `2009` and the `5001` and `5000` rows, requires `code` and `msg`, and the placement results are `anyOf` with the rule "a row carrying `code` is a failure row"; the `2017` row names the wrong-typed value of a strictly parsed body. (7) RE-CERTIFICATION: adopting the batch amendment or cancellation routes adds a surface outside your certified scope and is a material update of your trading system: re-certify with the Conformance Test Plan's rows X-07 to X-11 before you use them on production. (8) COMPANION DOCUMENTS: the complete code reference moves to revision 2.37, the Integration Pack to revision 3.11 (the transport matrix), the FIX rules of engagement to revision 2.24 (sections 2 and 7 name the two routes as not carried), the Member Technical Handbook to revision 2.17, the Conformance Test Plan to revision 1.4 (rows X-07 to X-11 and the batch evidence), the REST, WebSocket and FIX guides and the Postman guide and collection gain the two routes, and the REST, WebSocket, FIX and reference pages of the documentation site follow. Pinned by `BatchAmendCancelTests`, `BatchOrderTests`, `BatchRouteOpsTests`, `AuctionWireTests`, `FailClosedLicensingTests`, `ClusterGatewayTests`, `OpenApiExampleTests`, `CodeReferenceStructureTests` and `DocsDownloadsSyncTests`. |
| 3.73 | 2026-09-28 | THE EVENT STREAM AND HISTORY READS, BOUNDED, AND A POSITION CURSOR ON THE LAST PAGE (sections 4, 9, 10, 10.8 and 11, with a dated migration table in 11.3; /openapi.json info.version 2.36.0 to 2.37.0; complete code reference revision 2.36, the new 5007 row). The matching engine, its journal and everything it decides are untouched. (1) NEW CODE 5007 HISTORY_STORE_BUSY, HTTP 503 with data.retryAfterMs and Retry-After: a read of the venue's history store (GET /v1/stream/events, GET /v1/stream/cursor and the WS get.streamCursor, GET /v1/margin/calls, GET /v1/executions, GET /v1/orders/history, GET /v1/account/ledger, GET /v1/adl/events, GET /v1/reconciliation/end-client-positions) that the store could not answer, where these routes answered HTTP 500 with 5000; get.streamCursor acks 5007 where it acked 2013. On the event stream routes HTTP 503 now carries 5004 (this deployment has no event store: permanent) or 5007 (retry): branch on the code, never on the status. Nothing changed on your account; retry after the wait. (2) TECHNICAL BOUNDS ON THESE READS (section 4; the versioning policy's new sentence): on each node that serves you, a bound on how many reads of yours of the event stream routes, and of the other history routes above, run at once (the event stream group covers all your readers, keys and filters together), a budget of these reads in flight on the node that every member shares, a bound on how long one read may run, and a yield to the venue's own store writes. A read beyond the in-flight bound or the shared budget waits briefly for its turn, then answers 1003 with a suggested retryAfterMs (a 1003 while no other read of yours of the group is in flight comes from the shared budget, which serialising cannot help: back off); a read that takes longer than the time bound answers 5007 where it was served slowly before, and the group is then held for retryAfterMs; after repeated 5007 on the same read, narrow it. Keep one request in flight per stream and a client timeout of at least 15 seconds. (3) A POSITION CURSOR ON THE LAST PAGE (section 11.3; a correction in force at publication by the operator's decision of 2026-09-28, the venue being before production go-live, the 30-day notice of the versioning policy waived for it): a page of GET /v1/stream/events with hasMore false returns as nextCursor a cursor at the store watermark the page was read at, or at your toSeq when that is lower (seq that bound, eventIndex 2147483647, leg 9, the shape of the fromCursor a fromSeq request has always carried), never lower than your own cursor; a filter that matches nothing new then advances instead of re-reading the same range, and the cursor is valid only with the same channels and symbol. A loop that stops when nextCursor equals afterCursor no longer stops while the venue trades: stop on hasMore false or count 0. Resume after a restart from the nextCursor of your last page under the same filter, or from the cursor of the last event you processed when that is higher. From the same revision a channels value that names no family answers 5003. Pages with hasMore true are unchanged. (4) THE ENVELOPE ON A PARAMETER THE VENUE CANNOT READ, AND ON AN UNHANDLED ERROR (section 2's envelope rule, the 5003 and 5000 rows of section 9; a correction): a request whose required query parameter is absent (for example GET /v1/funding/history, GET /v1/klines or GET /v1/mark-klines without symbol) or whose parameter value does not parse as its documented type (a from, to or limit that is not an integer, an includeSkips other than true or false) answered HTTP 400 with an EMPTY body, which the envelope rule does not allow and an envelope parser cannot read; it now answers HTTP 400 with the envelope, code 5003 and a msg naming the parameter (for example symbol is required, limit must be a 32-bit integer). An unhandled error answers HTTP 500 with code 5000 and msg internal error as before, and its body now carries the full envelope, asOfSeq 0 and ts beside code and msg, where it carried code and msg only. The HTTP status is unchanged and no request that worked changes; branch on the code. Every request that worked at revision 3.72 works identically EXCEPT the answers of items (1), (2) and (3). |
| 3.72 | 2026-09-21 | THE CHANGE OF THE VENUE'S HISTORY STORE: ONE REFUSAL, ENGINE NEUTRAL TEXTS, NOTHING ELSE MEMBER VISIBLE (sections 2, 6d and 9; `/openapi.json` `info.version` 2.36.0; complete code reference revision 2.35). The venue moves the store behind its history reads (order, execution, ledger and funding history, the event stream backfill of section 11, statements, the identity records of section 6d) to another database engine, sandbox first, on a date venue operations announce in writing. The matching engine, its journal and everything it decides are untouched. No field, HTTP status, enum, default or example value is added, removed or renamed, and no figure changes; every request that worked at revision 3.71 works identically EXCEPT the refusal of item (1), which takes force on that same date, and the one note on a padded read filter inside that item. (1) NO SURROUNDING WHITESPACE IN A REFERENCE (section 2 "Identifiers", with the dated migration table). A `clOrdId`, a `clientUid` or a Trader ID of the `reg` block (`traderId`, `investmentDecisionId`, `executionDecisionId`) that begins or ends with a whitespace character is refused with `2013` and a sentence naming the rule, before anything is sequenced: on `POST /v1/orders`, per entry on `POST /v1/orders/batch`, on the RFQ routes, on the WebSocket `order.place` ack, on FIX as ONE ExecutionReport (`150=8`, `39=8`, `9010=2013`, for ClOrdID(11), Account(1) and the tags 9012, 9024 and 9025), and on `PUT` and `GET /v1/clients/{clientUid}/identity` and `PUT` and `GET /v1/trader-ids/{traderId}/identity`, which take the identifier from the URL path (the Trader ID PUT answered `2030` for such an id through its alphabet rule; the whitespace refusal answers first, one code for one fault). Why: the matching engine compares a reference character for character, the history records must never take two references that differ only by surrounding whitespace for one client or one order, and no documented alphabet ever contained whitespace. A cancel that names an order by reference (`DELETE /v1/orders?clOrdId=`, OrigClOrdID(41)) is a lookup and is never refused, so an order placed under a padded reference before that date stays cancellable under exactly that reference and by `orderId`, while any other padded reference answers `2014` (FIX: OrderCancelReject); amends and the FIX replace address the order by its order id and carry no reference; a read filter (`clientUid=` on the lot reads of section 6e) is a lookup too and is not refused, send it without padding, because the history reads ignore a trailing blank in a filter value; FIX TargetOpenClOrdID(9028) IS held to the refusal, with its own sentence, because it is resolved against the history records (target a lot opened under a padded reference by its lot id, tag 9020); and a member with registered Authorised Users still reads `2021` for a padded `reg.traderId`. (2) ENGINE NEUTRAL TEXTS. Three sentences of this document (section 6d twice, and the `5004` clause of its error list), the `503` descriptions of the two identity routes and one sentence of the `info.description` in `/openapi.json`, and the `msg` of `5004` on the client and the Trader ID identity routes no longer name a database product: they say the venue's history store ("client identity store requires the venue's history store, which is not configured on this deployment"). Codes and HTTP statuses are unchanged; branch on the code, never on `msg`. (3) A REGENERATED STATEMENT IS IDENTICAL. A monthly member statement generated again after the change carries the same figures in the same text, byte for byte, as the one generated before it, so its sha256 does not move; stored statements are not touched at all. The same holds for every decimal STRING this API serves: a figure keeps exactly the digits it had. (4) COMPANION DOCUMENTS, documentation only: the complete code reference moves to revision 2.35 (the `2013` row; its `5004` row never named the store), the FIX rules of engagement to revision 2.22 and the Integration Pack to revision 3.9; the REST, WebSocket and FIX guides and the Postman guide gain the refusal where they list the causes of `2013`, the REST guide and the Integration Pack drop the product name from the identity route rows, the architecture diagrams label the history store by its role, and the REST, WebSocket, FIX and reference pages of the documentation site follow. Pinned by `DoorRefusalWireTests` and `DoorRefusalFixTests` (the refusal per transport and per field), `EngineNeutralWordingTests` (the served document and every served string), `ClientIdentityTests` (the identity routes against the store) and `DocsDownloadsSyncTests`. |
| 3.71 | 2026-09-16 | FRACTIONAL BARRIER CONTRACTS AND THE 100 USD MINIMUM NOTIONAL (sections 2a and 6b; a LISTING revision on the operator instruction of 16 September 2026, decision ADR-25 beside ADR-18 section 5 item 11, the register entry pending; no wire schema change: no field, HTTP status, enum, default or example value is added, removed or renamed, no request changes, `/openapi.json` `info.version` stays 2.35.0 with ONE description string reworded in place, the BTC-USD-KO sizing sentence of the first `POST /v1/rfq` example (the perpetual's grid and the 100 USD floor in place of whole units), and the complete code reference stays at revision 2.34 with its `2028` row reworded in place under a dated parenthetical, the `2049` precedent). Every request that worked at revision 3.70 works identically on every non-barrier market. (1) SIZING. Every `*-USD-KO` market is listed with the quantity grid and the per-contract tick of the PERPETUAL of the same underlying, so on the KO rows of `GET /v1/instruments` and WS `get.instruments` `qtyStep`, `minQty` and `lotSize` move from `1` to the perpetual's step (`0.0001` on BTC-USD-KO; `0.001` on ETH-USD-KO, BNB-USD-KO, SP500-USD-KO, NDX100-USD-KO and XAU-USD-KO; `0.01` on SOL-USD-KO, WTI-USD-KO and BRENT-USD-KO; XRP-USD-KO, EUR-USD-KO, GBP-USD-KO and JPY-USD-KO keep `1` because their perpetual is whole-unit), `tickSize` and every quote price stay on the same display grid, `contractMultiplier` stays `"1"` (one unit of quantity is one unit of the underlying; fractional sizing is the grid, never the multiplier), `maxOrderQty` and `maxPositionQty` are unchanged, `maxOrderNotional` 10000000 USD stays the binding per-order cap and no `maxPositionNotional` is set. The RFQ `qty` is a decimal number of units on that step (`2002` off the grid, `2013` for a string that is not a decimal numeral, exactly as on order entry); integer quantity strings remain valid on every grid, so no example changes. Every barrier figure this document writes as `distance x qty x contract value` is unchanged and scale-free: 0.0015 BTC at a 70,000 USD mark with the barrier at 65,000 and the 1000 bps rebate is a premium of `6.750000` USD and a knock-out books exactly that premium, and a 0.5 BTC contract at entry 70,000 with the barrier at 69,900 books `45.000000` USD, the contract a whole-unit listing gave for 0.5 BTC of exposure. (2) THE FLOOR. `minNotional` is 100 USD on every barrier market (it was 5 USD; perpetuals, spot and swaps keep their floors). It applies where the venue applied the floor before: an opening request valued at the current mark (`2028` at create), every quote valued at its quoted entry price for the request's full size (`2028` at quote, so a request sized exactly at 100 USD at the mark can still be refused on a quote below the mark: size with headroom for the band), and a CLOSE REQUEST valued at the contract's whole quantity at the current mark, with the writer's close quote at its price. A contract whose whole quantity is worth less than 100 USD at the current mark therefore cannot be closed early by request and ends at its barrier, its take profit or its expiry, exactly as item (48) of revision 3.70 states for a contract above `maxOrderNotional`; termination and settlement are never floored, so a contract of any size always settles. The venue's `2028` sentence is unchanged. (3) ACTIVATION. On the sandbox the nine markets whose perpetual is fractional (BTC, ETH, SOL, BNB, SP500, NDX100, XAU, WTI and BRENT) are re-listed on the date venue operations announce: each is delisted, every contract still open on it is final-settled at the mark within its terms (`terminationReason` `FINAL_SETTLEMENT`, fee-free) unless its holder closed it early first, and the symbol is re-listed as a NEW instrument under a new `instrumentId` with the symbol unchanged, so symbol-keyed integrations follow by themselves while id-keyed history spans two ids per symbol; XRP-USD-KO, EUR-USD-KO and GBP-USD-KO keep their ids and take the 100 USD floor in place on the same date, and JPY-USD-KO takes it on its 1,000 JPY row with the JPY re-listing of item (47). Unlike that re-denomination a quantity keeps its meaning (a decimal of base units) and no price rescales: `"1"` BTC before the date and after it is the same contract. The same date is the announced date of the barrier `maxOrderNotional` (item (48)), the per-market `barrierGuard.entryBandBps` and the `maxContractLifeHours` values of revision 3.70 on the sandbox. Production lists every value from genesis. Re-read `GET /v1/instruments` after the date; do not carry cached barrier sizing across it. (4) COMPANION DOCUMENTS, documentation only: the Contract Specifications, USD-Settled Knock-Out (Barrier) Contracts move to Draft 0.2 (sections 1.2, 4.4, 6.2, 6.4, 7.2 with the Quantity Step and Minimum Notional columns and 8.5; the DRAFT banner stays until the Head of Legal's approval is recorded), the Member Technical Handbook to revision 2.16 (section 2), the Integration Pack to revision 3.8 (the thirteen table rows), the Postman guide's `2028` line, and the trading, REST and reference pages of the documentation site. Pinned by `BarrierQuoteGuardWireTests` (the specification and the thirteen rows on every surface), `DocsDownloadsSyncTests`, `VenueConfigTests` (both tracked configuration files hold every knock-out row to the perpetual's sizing with the 100 USD floor) and `BarrierFractionalSizingWireTests` (the wire at a fractional lot). |
| 3.70 | 2026-09-10 | The operator decision batch's revision: DESCRIPTION changes plus the ADDITIVE fields of items (5), (12), (15) to (19), (20) and (21), the read-time rules of items (13) and (14) and the portal rule of item (22) (sections 5, 6, 6b, 6d, 7, 9 and 10; `/openapi.json` `info.version` 2.34.0 to 2.35.0, the 3.56 and 3.60 precedent; complete code reference revision 2.34, the `2027`, `2030`, `2031` and `2039` rows). No field, HTTP status, enum, default or example value is removed or renamed and no request changes; the additions are items (5), (12), (15) to (19), (20) and (21), and every request that worked at revision 3.68 works identically. (12) Client identity (section 6d, MiFID II Article 58): `PUT /v1/clients/{clientUid}/identity` gains two OPTIONAL fields on both client types, `positionHolderCategory` (exactly the five Article 58(4) categories `INVESTMENT_FIRM_OR_CREDIT_INSTITUTION`, `INVESTMENT_FUND`, `OTHER_FINANCIAL_INSTITUTION`, `COMMERCIAL_UNDERTAKING`, `EMISSIONS_OPERATOR`; any other value `2030`) and `ultimateParentLei` (ISO 17442 with checksum, else `2030`; equal to `lei` means the client is its own parent), echoed on the PUT and on `GET /v1/clients/{clientUid}/identity` (omitted, like every null field of the echo, when not on file) and CLEARED by a PUT that omits them like every other field of the full upsert; a record stored before this revision carries neither. The venue files a daily position report under MiFID II Article 58(1)(b) for every position holder on a market flagged as a commodity derivative (the XAU, WTI and BRENT perpetuals and their knock-out markets), so the category is required in practice for every `clientUid` that holds such a position: it feeds the venue's weekly Article 58(1)(a) classification of position holders, and a record without it is still reported daily and is chased by venue operations for the weekly report (the daily layout is the nineteen fields of Implementing Regulation 2017/1093 Annex II Table 2 as replaced by 2022/1300, verified 2026-09-11). Trading is never blocked on either field. (13) Chase list (section 6d): a member whose licensing profile records that it reports its own transactions under MiFIR Article 26 (a MiFID II investment firm) receives an empty `clients` array on `GET /v1/clients/identity/missing`, the same envelope and `asOfSeq`; a read-time rule over the licensing profile, nothing else changes. (14) Retention (section 6d, new paragraph): a natural person's client or trader identity record is kept five years after the member reports the end of the relationship (MiFIR Article 25(2)) and may then be pseudonymised by venue operations, the identifying fields removed from the record and from every audit revision and the `clientUid` and revision skeleton kept; a pseudonymised record answers `2031` on the GET exactly as one never submitted (the `2031` row of the code reference and the two 404 descriptions in `/openapi.json` say so) and a new PUT starts a new revision; the "immutable audit revision" sentences are qualified accordingly; filed transaction reports are kept as filed. (1) Barrier accept guard (section 6b): the `2039` QUOTE_OFF_MARKET registry sentence (the same text on REST and the WebSocket ack) and the accept route's 400 description name the market's PUBLISHED band, `barrierGuard.entryBandBps` on `GET /v1/instruments` (50 bps unless the market sets its own), and the floors `barrierGuard.minTakeProfitBps` / `minBarrierBps` (10 unless the market sets its own), in place of the fixed figures, and the guard is described as in force on every environment. The band has been a rule of the market since revision 3.61: read the published value rather than carry your own constant. The per-market values of the sandbox listing: EUR-USD-KO, GBP-USD-KO and JPY-USD-KO carry 20 bps and XAU-USD-KO, SP500-USD-KO and NDX100-USD-KO 30 bps from the date venue operations announce on the seven Business Day track, every other knock-out market the 50 bps listing value; quoting members on those markets must quote within the tighter band. Every knock-out market lists at 50 bps and may be tightened per market by the published calibration rule (knock-out contract specification section 7.3); a change applies to acceptances after its publication and never re-prices an existing contract. Section 6b rules (4) and (5) name the floors `barrierGuard.minTakeProfitBps` / `minBarrierBps` (10 bps unless the market sets its own). A refused acceptance leaves the request AND the quote open (section 6b and the `2039` to `2043` rows of the code reference say so). New document `KronosX-Contract-Specifications-USD-Barriers.md` (DRAFT, not in force) on the downloads page, the first contract specification for the knock-out markets: the thirteen `*-USD-KO` markets and their per-market Trading Parameters table, the fixed contract multiplier of 1 (its section 6.2), the band's listing value and calibration rule (section 7.3), the maximum contract life (section 7.4, item (39) below) and the refusal clause (section 8.4). (2) Client identity (section 6d): one sentence on choosing `countryCode` for a natural person with more than one nationality (an EEA nationality over a non-EEA one, then the ISO 3166-1 alpha-2 code that sorts first; RTS 22 Article 6(3) and ESMA/2016/1452 section 5.5.1). No validation change: the venue stores the single `countryCode` you send and evaluates the identifier against that country's row; the Annex II table, including the GB row, is unchanged. (3) The `identifierPriority`, `nationalIdType` and `warnings` descriptions and the identity route's 400 description name the ENFORCE stage per environment: in force on production from its first day and on the sandbox from Tuesday 2026-09-22 (seven clear Business Days after this announcement); until that date the sandbox WARN stage stores a contradiction and reports it in `warnings`. (4) The `adlQuintile` descriptions on the position row and on `GET /v1/adl/indicator` carry the ranking basis: 5 = first to be auto-deleveraged, rank by unrealized profit ratio multiplied by account leverage, highest first, ties by ascending member id, losing positions below every profitable one, recomputed every 5 seconds. (5) Funding history (section 7): every `GET /v1/funding/history` row gains `settledTs`, Unix ms, the time of the settlement COMMAND that settled the anchor: equal to `settleTs` plus the timer latency in steady state, later than it for an anchor settled after a stale-mark deferral or a venue outage, and the same on every row of one catch-up command; OMITTED, never null, on rows recorded before this revision. `/openapi.json` gains the `/v1/funding/history` path itself (operationId `getFundingHistory`, tag Public market data, the full row schema with the LOCAL_DAILY figures): the route has been served and documented on the REST page since the venue's projections existed but was absent from the specification, the 3.56 precedent of documenting an already served surface. (6) Fees (section 7): once the venue publishes the Market Maker Scheme's measured obligations, `account.fees.criteria` on `GET /v1/account/parameters` carries them as the suffix ` (obligations: presence >= X percent, spread <= Y bps, size >= Z lots over N days)` for a member enrolled in the scheme; a string change only, no field, no OpenAPI move. The obligations are measured from your resting limit orders on the enrolled order-book markets (the share of minute boundaries with both sides resting, the median spread and the smallest per-side size over the window); an enrolment below them is refused and an enrolled member measured below them at the published cycle is withdrawn to the instrument rates with the section 36 notice. (7) Monthly IFR data annex (section 6f; a portal download, no `/v1` route, no OpenAPI move): after the K-TCD exposure snapshot the annex gains a swap receivables memo section (`asset,counterpartyType,receivable`), your taker-side staking swap receivables (principal plus the frozen reward, less what the writer has paid) by the WRITER's counterparty type in the contract asset at six decimals, a memo outside the netting sets that never names the writer; the section renders only for a month whose snapshot was captured after this revision, so every annex already downloadable is byte-identical. (8) Monthly IFR data annex v3 (section 6f; a portal download, no `/v1` route, no OpenAPI move): the annex header line moves from v2 to v3 and the methodology block gains the constant lines `barrierTreatment` (barrier contracts enter the derivatives flow at their canonical notional, quantity times entry price at the open and quantity times close price on an early close print; knock-out, take-profit, expiry, tear-up and final settlement terminations are fee-free BARRIERSETTLE ledger legs that print no trade and enter no flow measure; an early close prints a second trade that does) and `rcTreatment` (the replacement-cost treatment of the venue's pool-facing K-TCD snapshot: RC per netting set = your negative unrealised PnL from cost basis at the venue mark, IFR Article 28(1)(a) current market value, no variation settled before close-out; your own K-TCD uses the mirror, your positive unrealised PnL against the pool); the K-TCD section header labels the memo column `barrierMaxLossHeld` as the holder admission premiums held (the column set is unchanged); the line `capacityEnforcementCutover` follows `capacityCoveragePct` on every form (the venue parameter store row `capacity.enforcement.cutover` resolved at the month's pinned parameter version, empty when no row is in force; the daily flow CSV download carries the same line as in force on the day of the download); and, only where the venue puts a Business Day calendar in force, the lines `calendarMode` (rolling means divide by Business Days under the Rulebook V1.2 definition and the flow of a non-Business Day is attributed to the next Business Day; the daily series stays per UTC calendar day) and, per rolling measure, `calendarHolidaysApplied`; under the venue's current all-calendar-days convention neither calendar line prints and no figure moves. Every annex regenerated after this revision, past months included, carries the v3 header and the new lines, so its bytes and detached Ed25519 signature differ from a copy downloaded earlier; both remain a pure function of their inputs and verify against the published key. (9) Monthly IFR data annex, parameter-gated lines (section 6f; a portal download, no `/v1` route, no OpenAPI move): four lines the annex prints only once the venue parameter store row that resolves them is in force, so no byte of any annex moves until then. (a) Once the Rulebook amendment on the Membership Pool is effective, the header carries `# legalBasis,RULEBOOK_V1_3_POOL,<sentence>` naming the model (resolved at the month's last calendar day and the month's pinned parameter version, the empty-month form included) and the K-TCD section header states the pool novation; until then the annex asserts no counterparty-legal position and its bytes are unchanged; no figure, column or collateral view moves in either state. (b) Inside the month-end K-TCD block a `# riskFactorMethod` line, printed once the venue's compliance function has confirmed the Art. 26 risk-factor table for that snapshot's parameter version, names the rates and states that the risk factor is a venue-defined proxy over the licence types of the members holding the opposite side, applied to all of a member's open interest including positions held for clients; apply your own determination. (c) A `# ktcd factors,alpha,<a>,cva,<c>` line names the two K-TCD factors once both are confirmed for the snapshot's parameter version. (d) A `# ktcdMethodologyRevision,v2.1` line and six `# ktcdMethodology,` sentences state the computation in words with every figure read from the venue parameter store, once the venue puts the note in force for the snapshot; an unrecognised note revision prints `# ktcdMethodology,PARAMETER_ERROR`. The lines sit between the K-TCD section header and its column header in that order; the column set and the data row are unchanged; a line the store does not resolve is absent, never blank. Neither collateral view is your own funds requirement, and the venue expresses no view on whether any collateral may be recognised under the IFR. (10) Annex verification key id (Member Portal Guide 1.5, Member Technical Handbook 2.15 section 12a). Portal only; no `/v1` or OpenAPI change. The portal's signing-key read gains `keyId` (the first 8 lowercase hex characters of SHA-256 over the raw Ed25519 public key; null while the venue has not configured signing); the annex and its `.sig` download carry `X-KronosX-Annex-Key-Id` beside `X-KronosX-Annex-Signature-Ed25519`, and both headers now reach your HTTP client through the portal (a browser download stores the body only, so the Signature button remains the human path); the Regulatory tab shows the key id and public key beside the Signature button. Signatures issued since 3.31 are unchanged: the key id is a label for your key table, not a change to the key or the bytes. No engine, WebSocket frame or FIX change. (11) FIX parity of the production fail-closed licensing posture (ADR-15): an OrderCancelReplaceRequest (35=G) from a member with no licensing profile now answers OrderCancelReject 35=9 with `434=2`, `102=0` and `9010=2034`, exactly as the REST and WebSocket amend have since revision 3.28; the sandbox is unchanged (it never answers 2034), no field is added, removed or renamed, `/openapi.json` is unchanged by this item; the 2034 code row now lists the amend surfaces; FIX rules of engagement 2.19 to 2.20. (15) Regulatory order record (section 6, register DEV-MAP): one optional boolean `reg.riskReducing` on EVERY reg carrier (REST `POST /v1/orders`, the WS `order.place` op and batch entries, RFQ create, quote and accept, the lot close, FIX venue tag 9029 on NewOrderSingle, `Y` = true, any other value false, absent stays unset), RTS 22 field 64 on commodity-derivative markets, absent = false; recorded verbatim, so an order without it journals the identical reg string and hash as before, and it counts toward the 512 byte reg ceiling (2013). The venue's own report files it as field 64 on commodity-derivative legs and defaults to false when absent (section 6d); no new code. (16) EUR reference display (section 9 `GET /v1/account`, register CONF-331, Rulebook 14.3): a `EUR` asset row of a USD-settled venue gains `reference` `{rateCcy, rate, rateDate, source, equivalent, label}`, the informational, non-binding USD equivalent of the row's `total` at the ECB euro reference rate (four decimals as published, half even to six decimals), OMITTED (never null) on every other asset row and while no rate from the last five calendar days (unless the venue publishes another cutoff) is held; the WS `get.account` ack carries the identical block; Member Portal Guide 1.5. (17) `GET /v1/account/ledger` rows carry `ccy` after `kind` (section 10.3 cross-surface note, register BROKER-57), the same value the `wallet` frame and the stream `WALLET_ENTRY` row carry for the entry. (18) Executions (section 6.4, register BROKER-63): every PERP and BARRIER execution payload (the synchronous order answer, `GET /v1/executions`, the WS `execution` frame) and the RFQ accept answer (a PERP accept, a barrier entry, an early close; never a swap take) gain `uti`, the EMIR unique transaction identifier of the venue leg, the venue LEI plus the `tradeId` zero-padded to 12, derived at read time and omitted until the venue LEI is configured; FIX unchanged; pinned by `UtiWireTests`. (19) Public market data (section 5, register BROKER-78): `GET /v1/mark-klines?symbol=&interval=&from=&limit=`, mark-price candles `{t, open, high, low, close, ticks}` folded from the venue's one-minute mark history with the klines interval set, limit and bound, no volume, empty on a market without a mark; `GET /v1/klines` unchanged. (20) Contract identifier (section 6b, register BO-Q2; one additive field, `/openapi.json` schemas `BarrierContract.isin` and `RfqAcceptResult.isin`): every barrier contract row of `GET /v1/barriers` (live and closed, holder and writer alike) and the accept result of `POST /v1/rfq/{rfqId}/accept` on a barrier market (an opening and an early close alike) gain `isin`, the ISO 6166 ISIN of the CONTRACT's term set as the venue has it allocated by the Derivatives Service Bureau; null (and therefore omitted by the member API's envelope rule) until the venue has allocated it, always null where the venue files at market granularity (the sandbox), and never present on perp, spot or swap accepts. The WebSocket `barriers` frames and FIX are unchanged. On a production venue the barrier MARKET's own `isin` on `GET /v1/instruments` is null: barrier contracts carry their own identifier. (21) Dated barrier contracts only, CONDITIONAL (section 6b; `/openapi.json` `Instrument.barrierExpiryRequired`, the `barrier.expiryTs` description, the `2027` row of the code reference): BARRIER rows of `GET /v1/instruments` and WebSocket `get.instruments` publish `barrierExpiryRequired` (boolean; absent on other kinds). Where it reads true the venue lists dated contracts only and an opening request without `barrier.expiryTs` (or with 0) answers `2027` with the message `this venue lists dated barrier contracts only: send barrier.expiryTs` at the gateway, before anything is journaled; close requests, perp and spot RFQs and swap offers are untouched. The sandbox publishes false and admits open-ended contracts exactly as before; production announces a change of the flag to members first. (22) Member portal (Member Portal Guide 1.5; no `/v1` change): the portal's RFQ requests, quotes and acceptances apply the Trader ID gate exactly like `/v1` (`2021` once your member has registered Authorised Users, before `2024` and `2033`; cancels stay ungated); the RFQ tab carries one session-held Trader ID field applied to every RFQ write. A member with no registered Authorised Users sees no change. Items (15) to (21), (27) and (28) are ADDITIVE on every surface (a reader that ignores unknown fields is unaffected), share this revision's `/openapi.json` 2.35.0 (`RegFields.riskReducing`, `AssetBalance.reference` with the new `AssetReference` schema, `Execution.uti`, `RfqAcceptResult.uti`, the `/v1/mark-klines` path, `BarrierContract.isin`, `RfqAcceptResult.isin`, `Instrument.barrierExpiryRequired` and the `/v1/funding/history` `includeSkips` parameter with its `skipped` and `skipReason` row fields) and add no code (item (21) reuses the `2027` row), so the complete code reference stays at 2.34 (its section 5.5 gains the `skipReason` values and the funding frame type set); item (22) is portal only. (23) Trading hours (section 5, `/openapi.json` `Instrument.tradingHours`, ADDITIVE): every row of `GET /v1/instruments` and of the WebSocket `get.instruments` answer carries `tradingHours` (Rulebook Section 12): `CONTINUOUS` on every listing without an auction schedule, whatever its kind: 24 hours a day, every calendar day, weekends and public holidays included, no daily open, close or auction; trading is interrupted only by a suspension, halt or removal, by the circuit breaker or by a reduce-only or post-only session, each announced on `GET /v1/status`, on `status` there and on the `venueEvents` channel (section 10.9); `SCHEDULED` on a SECURITY market with an auction schedule (section 6j, corrected in place on 2026-09-11 before this revision shipped): its daily hours are the `auction` block of the row and the phase in force is `tradingPhase`. The value is an open set so that a further restricted-hours listing can publish another value without a breaking change: parse defensively, never assume the field is absent or fixed. (24) Market access on the swap markets (sections 6h and 7, the `3009` row of both references, the `Instrument.accessMode` description): the six swap markets `USD-TBILL-SWAP`, `EUR-TBILL-SWAP`, `BTC-STAKE-SWAP`, `ETH-STAKE-SWAP`, `SOL-STAKE-SWAP` and `USD1-STAKE-SWAP` are ADMISSION markets under the `SWAP_COUNTERPARTY` class of the Rules annex (client category eligible counterparty or professional client, an executed Swap Product Annex, conformance testing on the sandbox swap markets): only a member admitted to the market may post an offer on it or take one, `3009` otherwise, the code every market has answered a non-admitted member since revision 3.61; every perpetual, spot and barrier market stays OPEN. The six rows publish `accessMode` `ADMISSION` on the sandbox from the date venue operations announce (a member that holds an open contract on that day keeps it to maturity; the standing offers of a writer not admitted are withdrawn) and from their first day on production. Read `GET /v1/account/parameters markets[].access` for your own admission; apply through venue operations. (25) Funding rate source (section 7, the `FundingSchedule.rateSource` description): the description now states what `TICK` and `FIXED` mean on every read surface (`GET /v1/funding/current`, `GET /v1/ticker`, the WebSocket `get.funding` request and the `funding.{symbol}` rate frame read `fixedRate` under `FIXED`) and that the source is a rule of each market: read it per market, never assume one value across listings; the sandbox crypto perpetuals publish `FIXED` from the date venue operations announce. No field, default or example moved. (26) Family word: every `/openapi.json` description string that said "staking swap" now says "yield contract" (the fixed yield contracts of section 6h); no path, field, enum, example name or example value changed: `kind` `SWAP`, the `SWAP` flag, the six symbols and the codes `2036` to `2038` and `3008` are as they were. Items (23) to (26) are description changes plus the one additive `tradingHours` field; the complete code reference stays at 2.34 (its `3009` row's sentence). (27) SKIPPED FUNDING DAYS VISIBLE ON REQUEST (section 7, decision ADR18-A4; `/openapi.json` `/v1/funding/history` gains the `includeSkips` parameter and the `skipped` / `skipReason` row fields). ADDITIVE and OPT-IN: the default `GET /v1/funding/history` list is byte-identical, a member that sends nothing new sees nothing new. The operator decision, confirmed for production: a LOCAL_DAILY day whose position snapshot or in-window marks the venue did not record is omitted, no funding is exchanged for it and it is never charged at a later settlement. `GET /v1/funding/history?includeSkips=true` returns such a day as a row with `skipped` true and `skipReason` (`NO_POSITION_SNAPSHOT`, `NO_MARK_SNAPSHOTS`; an open set), `settleTs` and `snapshotTs`, no rate and no mark, interleaved newest first with the settled rows and counted against `limit`; a settled row never carries a `skipped` property; a value other than true or false answers 400 with no new code. (28) WebSocket `funding.{symbol}` (section 10) emits an additive `type: "skipped"` frame when a LOCAL_DAILY window closes without a settlement: `skipReason`, `settleTs`, `snapshotTs`, `settleCcy`, `ts`; best-effort like the two settlement frames, no stream backfill row, re-read the durable record with `includeSkips=true`. GRID markets never emit it. (29) The `funding.{symbol}` rate frame built from each mark tick on a GRID market whose `funding.rateSource` reads `FIXED` publishes the instrument's fixed rate as `predictedFundingRate` as well as `fundingRate`, exactly as `GET /v1/funding/current` and `GET /v1/ticker` already did; on a `TICK` market the frame is byte-identical (entry ADR20-Q10). Items (27) and (28) are ADDITIVE and OPT-IN (the `includeSkips` parameter and the `skipped` frame type of this revision's `/openapi.json` 2.35.0); item (29) changes no field and no schema; none of the three adds a code. (30) THE INSTRUMENT-RATE PIN (section 7, decided 2026-09-09, ADR-20 Q3; a behaviour and description row, no field added): a tier rate applies on a market only where it is LOWER than that market's instrument rate, compared per side; on such a market the `GET /v1/account/parameters` fee row carries the instrument rate with source `INSTRUMENT` and your tier rate under `inert`, and that is the fee deducted there (a VIP or scheme-tier member pays the yield contract markets' 1 / 2 bps instead of the tier's rates; a negative maker rebate is lower than any positive instrument rate and always applies). Section 7 also publishes the definition of traded notional (executed price times quantity in the settlement currency over every executed trade on the perpetual, spot and barrier markets where you are maker or taker, staking swap takes excluded, liquidation and ADL prints included, reversal legs counted and not netted), the trailing 30 calendar day window and the first-of-month 00:00 UTC cycle, and the Market Maker Scheme's monthly rule: a per-market programme's qualification is measured per UTC day against the obligations the venue publishes with it (presence, maximum own-quote spread, minimum size, window) and re-qualified every month; a missed month ends the enrolment at the next cycle (no schema change; the `criteria` suffix names the rule). `/openapi.json` 2.35.0 carries the description of the market fee rows in `AccountParameters` and of `source`. (31) ANNOUNCED DEFAULT-RATE CHANGES (sections 5 and 7; `/openapi.json` 2.35.0, ADDITIVE): every row of `GET /v1/instruments` and of the WebSocket `get.instruments` read may carry `pendingFees` `{makerFeeRate, takerFeeRate, effectiveFrom, effectiveFromIso}`, the announced change of the market's default maker and taker rates and the instant it applies, absent while none is pending; `account.fees.pending[]` on `GET /v1/account/parameters` gains rows of kind `INSTRUMENT` (`name` and `symbol` the market) for every market where you trade at the market's own rates. The rule: every change of the published fee schedule, the per-market default rates included, is announced at least seven Business Days ahead on the Website and here before it applies; a rate never changes before the published instant. No code added; the complete code reference stays at 2.34.(32) Order-to-trade regime per market (section 4, entry DEV-21): the ratio is measured per member per INSTRUMENT on the order-book markets, the class threshold applies to each market's own ratio and `1004` is answered on the market whose ratio is breached and nowhere else; a batch is gated per market as a whole; a cancel you asked for counts on its market from the venue's acknowledgement (a cancel-all one per cancelled order); RFQ quote messages and RFQ-mode markets are not counted; the account-wide ratio is a warning flag only. ADDITIVE fields on `GET /v1/account/parameters`: `account.otr.basis` (`PER_INSTRUMENT`), `account.otr.state` (`OK` or `WARN`) and `markets[].otr` (`orders`, `trades`, `ratio`, `state` `OK` `WARN` `BLOCK`, `windowMinutes`; absent on RFQ-mode markets), in this revision's `/openapi.json` 2.35.0; code reference 2.34 rewords the `1004` row. (33) Direct electronic access controls (section 4, entry ADR20-Q15): a tighter member-specific DEA-flow value, or a move to a lower DEA class, is a Rules measure (revision 3.70): venue operations record an objective ground and a case reference and you are notified by mail with the section 36 appeal route, exactly as for a rate-limit measure; the day's `usedToday` count is kept by the venue across a restart; no field, code or default moves, `3010` and `3011` unchanged. (34) The class ladder criteria (section 4, entry DEV-21): the `DMA` class is for members providing direct electronic access under the Company's DEA approval (Rules section 8) and the `MM` class for designated Market Making Members (a signed Market Maker Agreement and an active Market Maker Scheme enrolment); the criteria are checked for every member at the assignment; a description, no field moves. (35) Margin utilisation advisory (section 10.9, entry BO-Q9, option B; WebSocket only, no `/openapi.json` move, the revision 2.4 STALE_MARK precedent): the private `venueEvents` type `MARGIN_WATCH`, one frame the moment your utilisation (initial margin plus order margin over equity, the `GET /v1/account` figures) crosses the venue's early-warning threshold (`state` `ABOVE`) and one once it falls back strictly below the re-arm level (`CLEARED`), `data` `{state, ccy, utilisationPct, thresholdPct, reArmPct}`, `eventId` in the shared `advisory-{asOfSeq}-{n}` namespace; delivered to the member concerned on authenticated private sessions only, never public, never journaled, never in the `SNAPSHOT` or any backfill, repeated once after a venue restart or failover for an account still above the level; an early warning only, the `MARGIN_CALL` ladder is unchanged. ADDITIVE: a reader that ignores unknown types is unaffected; the type set of section 10.9 grows from ten to eleven; no code added, the complete code reference stays at 2.34 (its section 5.3 names the type). `marginWatchPct` on `GET /v1/account/parameters` (section 7) is the level the advisory and the mail to your trading contact fire at. Companion documents: the WebSocket integration manual, the Integration Pack, the WebSocket and reference pages of the documentation site. (36) SWAP MARKET FEES ARE THE INSTRUMENT RATES FOR EVERY MEMBER (section 6h; the `AccountParameters` market fee rows, the `source` description, the `Account` and `Instrument` fee descriptions of this revision's `/openapi.json` 2.35.0; ADR-19 decision Q3): on a swap market every member pays the market's `makerFeeRate` and `takerFeeRate` of `GET /v1/instruments`, fee tiers and programmes do not apply there, and the swap rows of `GET /v1/account/parameters` read source `INSTRUMENT` with a stored tier or programme value listed under `inert`; a behaviour change on the sandbox from the engine sequence venue operations announce (dormant below it, in force from genesis on production); no field removed or renamed, no code added. (37) SWAP WRITER DESIGNATION (section 6h; the `POST /v1/rfq` (swap market) and accept rows; the code table; the complete code reference 2.34 gains the `3013` row; ADR-19 decisions Q4 and Q6): posting an OFFER on a swap market is reserved to Members the venue has designated as Market Maker Members under the Rules, and only on their own account (no `clientUid`, or the house uid); any other member's offer, and a Market Maker Member's offer on an end-client account, answers the new code `3013` SWAP_WRITER_ROLE; taking is unchanged. A withdrawn designation withdraws standing offers at the next take (`3007`), not on the day of the withdrawal. A withdrawn designation also ends, on the day of the withdrawal, the Market Maker Scheme tier and any rate-limit or OTR class that required it: from that write your fees read the instrument rates and your throttles the STANDARD class (section 4), each named in the notice you receive (code audit 2026-09-11). WARNING for a BOTH member whose bot posts offers under end-client uids (the sandbox bots included): those offers answer `3013` after activation. (38) `swapWriterEligibility` on SWAP rows of `GET /v1/instruments` and WebSocket `get.instruments` (ADDITIVE; `/openapi.json` `Instrument.swapWriterEligibility`, enum `ANY` and `MARKET_MAKER`, an open set): `MARKET_MAKER` while the rule of item (37) is enforced, `ANY` while it is dormant, so the published value is always the enforced rule; the sandbox activation sequence is announced by venue operations and read from this field, production publishes `MARKET_MAKER` from the first day. Items (36) to (38) share this revision's `/openapi.json` 2.35.0; item (37) adds the `3013` row to the complete code reference at revision 2.34. (39) MAXIMUM CONTRACT LIFE PER BARRIER MARKET (section 6b, register BROKER-86, ADR-20 row A-11; `/openapi.json` `Instrument.maxContractLifeHours`, the `barrier.expiryTs`, `contractExpiryTs` and `expiryTs` descriptions and the open-ended example's note; the `2027` row of the code reference; ADDITIVE): BARRIER rows of `GET /v1/instruments` and WebSocket `get.instruments` may carry `maxContractLifeHours`, the longest life of an OPENING contract in whole hours measured from the request; absent where the market sets none, and an omitted `barrier.expiryTs` is then open-ended exactly as before. Where it is published an opening request without `barrier.expiryTs`, or with one later than the request time plus the cap, answers `2027` (the barrier-terms code; an accept can still land up to the quoting window later, so a contract's actual life may exceed the cap by at most that window); close requests, perp and spot RFQs and swap offers are untouched, and open contracts run to their terms. A rule of the market, journaled and replay-identical, applied to a listed market only after the seven Business Day notice: the sandbox's thirteen `*-USD-KO` markets carry 8784 h (366 days) in the tracked configuration and take it live on the date venue operations announce. Pinned by `ParamScopeWireTests`. (40) DEFAULTER-SIDE ADL ATTRIBUTION (sections 6e, 7, 10, 10.9 and 11, register BROKER-102; `/openapi.json` gains the `/v1/adl/events` path and the `AdlEventRow` schema, both documented for the first time; ADDITIVE, no code added): the `adl` execution frame and its backfill row gain `role` (`DEFAULTER` \| `DELEVERAGED`, which side of the tear-up the row attributes), `side` (`BUY` \| `SELL`, YOUR own close direction) and `positionSide` (`LONG` \| `SHORT`, omitted for the netting bucket). A defaulting member receives one `adl` execution per end client the venue closed in a tear-up (role `DEFAULTER`, its own `clientUid`, `positionSide`, `qty`, the capped bankruptcy `price`, `tradeId`, `episodeId`), the same row a deleveraged member already received (now role `DELEVERAGED` with its side); the counterparty never sees the defaulter's identifiers. `GET /v1/adl/events` carries `role`, `closeSide` and `positionSide`, and the `clientUid` on the defaulter's own rows (`side` keeps its meaning there, your relation to the episode). `GET /v1/lots/closures` lists the defaulter's ADL closures (flags `ADL`, `closeClOrdId` `KX-ADL-{tradeId}`, `unsolicited` true). Rows recorded before this revision carry no `role` or `side` (`role` reads `DELEVERAGED` on the REST route, every one of them a counterparty row). The defaulter rows are in force on the dev venue from engine sequence 1,500,000,000 (announced by venue operations; from genesis on production); `DELEVERAGED` rows carry `role` and `side` from this revision's first boot. The member-level `venueEvents` `ADL` frame with role `DEFAULTER` stays exactly one per trade. Pinned by `DefaulterAdlAttributionWireTests`. (41) BARRIER NOTIONAL ORDER CAP, documentation and pin only (section 6b, register ADR18-A7): a BARRIER market may carry `maxOrderNotional` like any other kind; where published it bounds `POST /v1/rfq` (valued at the current mark) and the writer's quote (valued at the quoted entry) with `2013`, and the member-market and self arms of `GET /v1/account/parameters` apply to it exactly as on order entry (a self value may only tighten it, `5003` otherwise; the market cap itself is read on `GET /v1/instruments`, never listed under `inert`). No wire change and no OpenAPI text moves; pinned by `BarrierNotionalOrderCapWireTests`. The tracked sandbox configuration carries `maxOrderNotional` 10000000 USD on every `*-USD-KO` row (item (48)); it binds on the sandbox from the date venue operations announce. (42) GRID funding catch-up fence (sections 6, 6b, 7 and 9; the operator decision batch, entry SEC-F13): while a GRID perpetual's funding settlement is more than 60 s overdue the venue is settling the missed anchors (each priced on the positions held when it lands) and refuses NEW risk on that market until the catch-up has landed: an opening order, a non-reduce-only amend and an RFQ accept answer the new code `3014` FUNDING_CATCH_UP (the same text on REST, the WebSocket ack and FIX `Text(58)` with `9010=3014`), reduce-only flow stays admitted, and a conditional order whose trigger fires inside the window is canceled with the new `cancelReason` value `FUNDING_CATCH_UP` (VENUE-INITIATED; the value is appended to the open set, every existing value is unchanged). After a restart the venue lands the catch-up before it serves any member and re-fires an overdue settle every second, so the window normally lasts seconds; the `FUNDING_SETTLED` event is the lift signal. Dormant on the sandbox until the decision batch epoch announced by venue operations, live on production from the first day. A description-only change to `/openapi.json` (both order-entry 400 descriptions name `3014`; `info.version` stays 2.35.0); the `3014` row, the `FUNDING_CATCH_UP` row of the `cancelReason` table and its retry-matrix row join the complete code reference at revision 2.34, and the FIX rules of engagement move to 2.21. (43) THE FEE ANNEX RULE STATED (section 7; documentation only, no field or schema change): every change of the published fee schedule, a tier or programme rate, a band or criterion, a scheme term, the activation of the order-to-trade surcharge, and the per-market default rates, is treated as a material amendment and announced on the Website and on this API at least seven Business Days before its effective date (Business Days on the Cyprus calendar); the announcement reaches you as `account.fees.pending` on `GET /v1/account/parameters` and as `pendingFees` on the instrument reads. Recorded with the closure of the venue's ADR-20 deviation items (register revision 4). (44) DOCUMENTATION only (section 11.4): the venue's seven-year transaction record retention and the request procedure (your own records only, never counterparty identities, within ten Business Days) are stated beside the 35 day event stream retention. No field, default or request shape changes; `/openapi.json` `info.version` stays 2.35.0; FIX unchanged. (45) TRADING HOURS AND THE MAINTENANCE WINDOW STATED, DOCUMENTATION only (sections 1a, 5, 6, 7 and 10.7; no field, default or request shape changes; `/openapi.json` `info.version` stays 2.35.0; FIX unchanged): the Trading hours paragraph of section 5 states the rule behind `tradingHours` of item (23) as the trading hours of each market under Rulebook Section 12 (continuous trading 24 hours a day on every calendar day, weekends and public holidays included, no daily open, close or auction; interruptions only by a suspension, halt or removal, the circuit breaker or the reduce-only and post-only sessions; the venue's standard maintenance window Saturday 02:00 to 06:00 UTC as the only scheduled interruption, published as a standing exception, maintenance inside it announced at least 48 hours ahead, a release that requires the venue to stop announced at least five Business Days ahead with the date, the UTC start and the expected duration; a scheduled closure outside the window on the material-amendment track; a software release is never a closure); section 1a states that the sandbox is available on a best-efforts basis and observes the same window; section 6 carries the closed-day sentence; section 7 LOCAL_DAILY item 5 states the closed-day funding rule the venue has applied since revision 3.42 (one settlement every calendar day, the underlying's closed days included, at the last available values of the venue's reference price source, the rate bounded by `funding.rateCap` and `funding.rateFloor`), confirmed by venue operations on 2026-09-10; section 10.7 gains the planned maintenance window rule. Companion documents: the contract specifications for the USD-settled perpetuals, the spot markets and the staking and yield swaps at Draft 0.2 (each with a Trading Hours section and the trading hours as a Trading Parameter; the perpetuals with the funding rate source, the liquidation parameters, the daily local settlement clause and the per-sub-position realised profit and loss; the swaps with the calendar-day tenor, the day-count disclosure, the venue-halt deferral, the default-management freeze and the identification of a DEFAULTED Writer on written request), the NEW Contract Specifications, USD-Settled Tradfi Perpetuals at Draft 0.1 (the eight tradfi listings, one unit = 1,000 JPY on JPY-USD-PERP), the NEW Contract Specifications, USD-Settled Knock-Out (Barrier) Contracts at Draft 0.1 (item (1)), Member Technical Handbook revision 2.15 section 2, the REST and trading pages of the documentation site. (46) DOCUMENTATION only, no wire change (section 7; no field added, removed or renamed, no default moved, `/openapi.json` `info.version` stays 2.35.0, the complete code reference stays at 2.34, and every request that worked before this item works identically). (a) Section 7 (margin model) now states the liquidation tranche in the words of the Rulebook V1.3 section 20 text: a market order for the tranche size published for the market, never below the published minimum tranche and never more than the remaining position, price banded around the current mark by the published band in the interest of orderly execution (an immediate-or-cancel limit at the mark plus or minus the band on the wire; the values in force with their source are `liquidation.slicePercent`, `minSliceQty` and `aggressionBps` on `GET /v1/instruments` since 3.61). Companion documents: Netting and Unsolicited Flow Statement Version 1.4 (section 3 item 2), Contract Specifications USD Perpetuals Draft 0.2 (Section 7.1 lists the three parameters). (b) Additional member accounts (subaccounts) on request: the Member Technical Handbook section 7 (revision 2.15) and the Member Portal Guide section 12 (revision 1.5) describe the request path for a further member account under your membership: the rulebook's Position Margin election is honoured by a separately margined account, collateral moves between your accounts only by withdrawal and deposit, every limit and parameter applies per account, a measure under the Rules applies to all of your accounts, and self-trade prevention does not apply between your own accounts. (c) The Netting and Unsolicited Flow Statement moves to Version 1.4 (10 September 2026): an auto-deleveraging close is fee-free on both legs in both of its cases (the only ledger entry an ADL execution books is the realized profit or loss, kind `ADLSETTLEMENT`); the trade reversal mechanism names its rulebook power as section 27 and states the restoration standard and the fee and funding rule (the compensating trade restores the parties so far as reasonably practicable, realised profit and loss settles against the cost basis at the time of the reversal, and no funding settlement, liquidation or auto-deleveraging between the two trades is unwound; the compensating trade is fee-free; a venue-initiated reversal refunds the original trade's fees to both parties and compensates the net funding attributable to the reversed lots as separate ledger entries of kind `MANUALADJUST`, `refId` 0, on the `wallet` channel and `GET /v1/account/ledger`; a reversal both parties requested retains the original fees and books nothing; `MANUALADJUST` is the ledger `kind` value of a venue manual adjustment, served since the ledger surface existed and first named in this revision, the 3.56 precedent of documenting a served value; no route, channel or code changes and FIX is unchanged); and a new section 6, Record and reproduction, states how the sequenced record is kept and how a calculation is reproduced from it. (d) Member Technical Handbook 2.15 gains section 12a, verifying the monthly IFR data annex (what is signed, where the key is, the key id convention, the key table with the sandbox key id `775faab7` and the production row published at go-live, and a verification example in Python and OpenSSL worked against the RFC 8032 section 7.1 TEST 1 vector), the per-account and self-trade prevention sentences of section 7, and the determinism bullet of section 1 now says that a dispute about a figure is answerable by reproducing it from the verified snapshot with the binary version that produced it, the same sentence as on the overview page of the documentation site. (47) JPY RE-DENOMINATION (section 2a, decision ADR18-A5; a listing revision, no wire schema change: no field is added, removed or renamed and `/openapi.json` `info.version` stays 2.35.0). JPY-USD-PERP and JPY-USD-KO are re-listed with one base unit = 1,000 JPY: `tickSize` 0.0001 (USD per 1,000 JPY, the same granularity as 0.0000001 per JPY), `qtyStep` = `minQty` 1 (= 1,000 JPY), `minNotional` 5 USD; symbols, quotation direction (USD per JPY), USD settlement, the LOCAL_DAILY funding row and `baseAsset` JPY are unchanged. On the sandbox the previous 1 JPY listings (instrument ids 13 and 25) are delisted and final-settled at the mark on the date venue operations announce and the symbols re-listed as new instruments (ids 37 and 38); every price stored on these symbols before that date is per 1 JPY and every price after it per 1,000 JPY (a factor of 1,000), so history-reading integrations key on the listing date; production lists the 1,000 JPY rows from genesis. Section 2a carries the explicit unit exception; the Contract Specifications, USD-Settled Tradfi Perpetuals state the unit for the tradfi listings; the Integration Pack moves to revision 3.7. (48) BARRIER LISTING per-order cap (section 2a, decision ADR18-A7, ratified 2026-09-10; the listing behind item (41): no field is added, removed or renamed, `/openapi.json` `info.version` stays 2.35.0). Every `*-USD-KO` row of `GET /v1/instruments` (and WS `get.instruments`) publishes `maxOrderNotional` 10000000 (USD), the venue standard every perpetual and spot listing already carries, and still no `maxPositionNotional`. In force from the moment the value is published on a market: an opening request (`POST /v1/rfq`) valued at the current mark, a quote valued at its quoted entry for the request's full size, and an early-close request valued at the contract's size, each reject `2013` above 10,000,000 USD (before, only the base-unit `maxOrderQty` bounded them); `maxOrderQty` becomes informational legacy on those rows; an open contract above the cap can no longer be closed early by request and ends by knock-out, take profit or expiry. Your own tighter `maxOrderNotional` for a `*-USD-KO` market under `PUT /v1/account/parameters` is accepted (it answered `5003` while the market carried no notional cap). The tracked sandbox configuration carries the value on all thirteen rows; it binds on the sandbox per market from the date venue operations announce (the journaled upsert), production lists it from genesis. The same decision ratifies the 10,000,000 USD per-order standard on every listing with a price and the 2,000,000,000 USD position standard on every perpetual. FIX is unchanged. Companion documents: Integration Pack revision 3.7 (section 3 listing table). (49) TRADING PARAMETERS OF THE SWAP MARKETS (section 6h, decision ADR19-Q7; documentation and per-market parameters only, NO wire change, `/openapi.json` `info.version` stays 2.35.0). The per-offer / per-take cap (`maxOrderQty`) on BTC-STAKE-SWAP moves from 1,000 to 100 BTC and on ETH-STAKE-SWAP from 10,000 to 5,000 ETH; `maxPositionQty` stays ten times the cap (1,000 BTC, 50,000 ETH), so one offer or take on every swap market is worth at most about 10,000,000 USD at the listing marks, the value of the USD, EUR and USD1 caps and of the perpetuals' per-order notional cap. The four other swap markets, the tenor bounds (7 to 364 days on the bill markets, 1 to 365 on the stake markets), the rate caps (2000 and 3000 bps) and the USD funding rule (a USD take is admitted within your available margin balance and never while you are in default management) are confirmed unchanged. Applied to the sandbox on the date venue operations announce (the tracked configuration carries the values): a standing offer keeps its face size until withdrawn or lapsed, every take is bound by the new cap at once, existing contracts keep their frozen terms. The values in force are always the ones on `GET /v1/instruments`. (50) DOCUMENTATION revision, the swap conventions and the default-management rules confirmed (section 6h, decisions ADR19-Q2 and ADR19-Q8; no wire change, no field added, removed or renamed, no default moved, `/openapi.json` `info.version` stays 2.35.0, and every request that worked at 3.68 works identically). The three conventions served since revision 3.51 are the venue's rules, no longer assumptions: (1) maturity is exactly `tenorDays x 86,400,000 ms` after the take's venue timestamp, in calendar days including weekends and holidays (the Rulebook's Business Day definition does not apply to the tenor), settled on the first 1 s venue timer tick at or after it, deferred only by a venue-wide halt (the first tick after resume) and never by a halt of the swap market; (2) the reward is `floor(notional x rateBps x tenorDays / 3,650,000)` in the asset on an actual/365 fixed basis, rounded down to the sixth decimal, frozen at the take and never re-priced (a later change of a market's `swapMaxRateBps` or tenor bounds is checked on new offers only, standing offers and open contracts keep their terms; removal from trading pays the reward pro rata by whole elapsed days, rounded down, never more than the frozen amount); (3) the rate is a simple annualised rate, not a discount yield, and is not directly comparable to a Treasury bill yield quoted on an actual/360 discount basis. Worked examples unchanged: 1 BTC at 450 bps for 30 days pays `0.003698` BTC, 10,000 USD at 420 bps for 91 days pays `104.712328` USD. The default-management rules served since 3.51 are confirmed by venue operations (decision of 9 September 2026) as the contract terms: a contract rides through either party's liquidation untouched; while the writer is in default management on the venue (in liquidation, in default, or with an open unwind) no amount is paid out under a contract, an amount due falling in that period is recorded in full as the taker's claim (the `swaps` frame `default` with `amount` 0 and `outstanding` equal to the due, `GET /v1/swaps` status `DEFAULTED`) and is paid on the first settlement cycle after default management ends from the writer's then freely available balance in the asset (`cured`); the venue pays no claim from its own funds; the venue's powers under the Rulebook's Liquidation of Positions and Default of a Member provisions are reserved; there is no early termination; a removal from trading settles at principal plus the reward accrued pro rata by elapsed whole days (`finalSettlement`). A `DEFAULTED` contract with `amount` 0 at maturity may therefore be a deferral rather than a shortfall of funds; members offering swaps to their own clients should reflect this in their client disclosures. Companion document: `KronosX-Contract-Specifications-Swaps.md` Draft 0.2 (sections 4.2, 4.3, 5.3, 5.4, 9.1 and 9.5). FIX unchanged. (51) PRODUCT NAMES AND REPORTING SCOPE OF THE SWAP MARKETS (sections 6a, 6d and 6h, decisions ADR19-Q5 and BROKER-25; DOCUMENTATION revision: no wire change, no field added, removed or renamed, no default moved, the family word of item (26) already in `/openapi.json` 2.35.0). (1) Product names of the six SWAP markets confirmed (section 6h): USD, EUR, USD1, BTC, ETH and SOL Fixed Yield Contract, family word yield contracts; the earlier names T-Bill Yield Swap, Euro Bill Yield Swap, BTC / ETH / SOL Staking Swap and USD1 Yield Swap are withdrawn; the symbol tokens TBILL, STAKE and SWAP are identifiers only; symbols, kind, channel, ledger kinds, flags and codes are unchanged. (2) Reporting scope stated: by the venue's decision of 10 September 2026 the six SWAP markets are within the MiFIR scope (RTS 22 transaction reports on every take with the rate in basis points and the size in the asset, RTS 24 offer records, RTS 23 reference data); the venue's reporting record admits them from the date venue operations announce, from which `GET /v1/clients/identity/missing` lists swap-market `clientUid`s (until then it lists perpetual and barrier market `clientUid`s only); `clientUid` and the section 6d identity duties apply on every offer and take. (3) Contract Specifications, Fixed Yield Contracts, Draft 0.2 re-headed accordingly (the names, the classification, sections 3.2, 7.1, 8.1 and 11.1); the complete code reference (revision 2.34), the FIX rules of engagement and the FIX integration guide carry the family word (wording only); the trading, REST, WebSocket and reference pages of the documentation site follow. (52) SWAP MARKET ADMISSION, the documentation behind item (24) (sections 6h and 9, the `3009` row of both references, decision ADR20-Q9): the three criteria of the `SWAP_COUNTERPARTY` class are stated in section 6h (the client category recorded for the member is eligible counterparty or professional, the Swap Product Annex to the Membership Agreement executed, conformance testing on the test environment's swap markets passed under the new Section W of the Conformance Test Plan, revision 1.2); the admission covers both roles on the market and is granted per market; a writer whose admission lapses has its standing offers refused at the next take (`3007` to the taker) and withdrawn and cannot post new ones, open contracts run to term; a refused admission goes to the complaints procedure of Rules section 38 (a `DENIED` measure appeals under section 36); the change is announced seven Business Days ahead under Rules section 43 and each affected member notified under section 9. Contract Specifications, Fixed Yield Contracts sections 3.2 and 7.1 carry the same rule. No field, route or default changes; FIX unchanged. (53) THE PUBLISHED LADDER NUMBERS (section 4, entry ADR20-Q11; DOCUMENTATION AND ONE PUBLISHED TIGHTENING: no field is added, removed or renamed and no default moves; `/openapi.json` `info.version` stays 2.35.0 and its `ordersPerSecBurst` example reads the production STANDARD value 1000, the sandbox live read 0; the complete code reference stays at 2.34, its `1003` row naming the numbers; Member Technical Handbook 2.15). The three classes `STANDARD`, `DMA` and `MM` are published with their criteria, per-minute rates and per-second ceilings (one fortieth of the per-minute order rate): STANDARD 6,000 reads and 40,000 orders per minute with 1,000 orders per second, DMA 12,000 / 120,000 / 3,000, MM 12,000 / 200,000 / 5,000; the OTR window 60 minutes and floor 100 restated; the first-quarter warning thresholds on production stated (STANDARD 500, MM 5,000, no block threshold; block thresholds by annex amendment after a measured quarter). STANDARD gains a per-second ceiling of 1,000 orders/s on the production venue, effective after the seven Business Day notice of Rules section 43; the sandbox keeps `ordersPerSecBurst` 0. The member portal's API and limits page shows your class and, where published, the ceiling. FIX is unchanged. Companion documents: Member Technical Handbook 2.15, the REST, WebSocket, FIX and Integration Pack manuals, the REST, WebSocket, FIX and reference pages of the documentation site. (54) ORDER-TO-TRADE SURCHARGE NOT IN FORCE (section 4, entry ADR20-Q4; DOCUMENTATION only: no wire change, no field added, removed or renamed, no default moves, every request that worked before this item works identically; `/openapi.json` unchanged, its description of `account.otr.surcharge` already reads applied by nobody while `enabled` is false). The prepared `account.otr.surcharge` row is now described as NOT IN FORCE by venue decision: no order-to-trade surcharge applies, and one may be introduced only by an amendment of the published fee schedule announced at least seven Business Days before it takes effect, as a measured criterion of the published fee cycle, instead of as a fee in basis points while a member sits above its class ratio; `enabled` false and `bps` null on the read are unchanged. (55) ANNEX II ENFORCE DATE PER ENVIRONMENT IN SECTION 6d (entry BO-Q12; the section text behind item (3); the `2030` row of the complete code reference at 2.34; no wire change: every request that stores today without a warning stores identically). The RTS 22 Annex II rollout stage is now dated per environment in section 6d instead of "announced by venue operations": production is in the ENFORCE stage from its first day; the sandbox switches from WARN to ENFORCE on Tuesday 2026-09-22, announced 2026-09-10 with seven clear Business Days notice. From that date a contradiction of the Annex II table (a `nationalIdType` the country does not designate, an `identifierPriority` that contradicts the table, a national identifier failing the country's format check, no `nationalId` for EE, ES, IS, IT, MT or PL) rejects `PUT /v1/clients/{clientUid}/identity` and `PUT /v1/trader-ids/{traderId}/identity` with `2030` and stores nothing; a plain warning still stores and is still reported in `warnings`; trading is unaffected either way. Records stored with a contradiction before the switch stay on file. Clear your sandbox warnings before 2026-09-22: the venue's own re-validation on 2026-09-09 found no stored contradiction. Companion documents: REST integration manual, Integration Pack, the REST and reference pages of the documentation site. (56) THE GB ROW (section 6d, entry DEV-UKID; DOCUMENTATION only: no wire change, no field added, removed or renamed, no default moved, `/openapi.json` unchanged, every request that worked before this item works identically). One paragraph records that UK nationals are identified under the GB row of RTS 22 Annex II as printed (National Insurance number, then CONCAT), which is what the venue has applied since revision 3.53; a `PASSPORT` label for `GB` draws the undesignated-label warning and, in the ENFORCE stage, `2030`. (57) WHAT THE IDENTITY RECORD IS FOR (section 6d, entry BROKER-44; the wording behind item (13); DOCUMENTATION only, no wire change): the identity record serves the venue's own regulatory reporting only, its MiFIR Article 26(5) transaction reports and, where a surveillance alert concerns the client, the suspicious transaction and order report (STOR) the venue files with CySEC; it is never read by matching, margin or any risk path and is disclosed to no other member. Companion documents: REST integration manual, Integration Pack, the REST page of the documentation site. (59) PORTAL TRADER ID GATE AND SUBMISSION CHANNELS (sections 6 and 6g; portal only, no `/v1` change; complete code reference revision 2.34, the `2021` row; Member Portal Guide 1.5). The member web portal's RFQ requests, quotes and acceptances are Trader-ID gated exactly like the API: staged per member, a member with no registered Authorised Users sees no change, otherwise a registered active `reg.traderId` is required and refused with `2021`; the RFQ tab carries a Trader ID field; cancels are never gated. Submission channels stated as built: orders and quotes over REST, WebSocket and FIX; the RFQ lifecycle additionally through the portal; the portal places book orders on SECURITY markets only, from its DLT Market tab, as item (80) and section 6g state (Member Portal Guide section 10a), and no book orders on any other kind. FIX is unchanged. (60) THE REPORT-LABEL RULE (section 6f; DOCUMENTATION only, no wire change, no field added, removed or renamed, no default moves). The per-member RTS 22 field 29 mapping first described at 3.29 exists only as a report label read when the venue renders your transaction report or its RTS 22 extract, never at order entry and never in K-DTF / K-COH bucketing; the only label the venue ever configures is MTCH reported as AOTC, set where the venue's compliance function has determined on its one published test that your own-name commission business is not matched principal trading under RTS 22 as applied by CySEC, evidenced by your capacity-tagging attestation; DEAL and AOTC always report as themselves; a member that reports its own transactions under MiFIR Art. 26 carries no label because the venue does not file its legs. Companion documents: Member Technical Handbook 2.15 (section 10a), the REST page of the documentation site. (61) PRODUCTION POSTURE OF CAPACITY ENFORCEMENT (section 6f; DOCUMENTATION only). On the production venue every member's licensing profile is at the ENFORCE stage before its first order and every DEAL-permitted member, market makers included, has its house reference configured before its first order, so code 2033 applies from the first production order and no pre-enforcement capacity history exists on production; the earlier wording (enforcement begins when venue operations agree the stage with you) now describes the sandbox only. Also corrected in 6f: the engine defaults an empty `clientUid` to your house reference only for a market-maker member with one configured and answers 2010 otherwise (the note used to say non-market-maker flow). Journaled capacity records are never reclassified (unchanged since revisions 3.25 and 3.27) and every statement keeps its coverage percentage. Companion documents: documentation site reference page (code 2033 row) and REST page (`reg.tradingCapacity` row), Member Technical Handbook revision 2.15 section 10a. (62) AVERAGING CALENDAR OF THE ROLLING IFR MEANS (section 6f; portal only; Member Portal Guide 1.5). The averaging calendar of the rolling K-DTF and K-COH venue-attributable figures is stated as the venue's published convention, confirmed by venue compliance on 2026-09-10: every calendar day of the window counts at the 00:00 UTC boundary, a day without fills as zero, no day excluded or reattributed because the venue lists no non-trading day; this is the lower of the two readings against a business-day denominator, and a member on a business-day basis rebases from the daily series CSV (up to 400 days). The rolling means divide by the calendar the venue names for the measurement date: every UTC calendar day (`ALL_DAYS`, the convention in force) or Cyprus Business Days (`BUSINESS_DAYS_CY`, non-Business-Day flow attributed to the next Business Day, a venue methodology); the venue switches on a dated parameter row and this row is the notice of the mechanism. The monthly IFR data annex carries the `# calendarMode` and `# calendarHolidaysApplied` header lines only when measured on the Business Day calendar; every annex measured on `ALL_DAYS`, including every annex already downloaded, regenerates byte-identical with its detached signature. The portal Regulatory tab's rolling cards read the day count as Business Days under that calendar and list the bank holidays applied. (63) BARRIER TREATMENT AND THE K-TCD MEMO COLUMN (section 6f; portal statements). The annex methodology block's `barrierTreatment` line states that barrier contracts enter the derivatives flow at their canonical notional (quantity times entry price at the open, quantity times close price on an early close print), that knock-out, take-profit, expiry, tear-up and final settlement terminations are fee-free BARRIERSETTLE ledger legs that print no trade and enter no flow measure, and that an early close prints a second trade that does; the K-TCD snapshot header names the memo column `barrierMaxLossHeld` as holder admission premiums held (the premium each open contract margined at its open), never a notional. Section 6f carries the same sentences and names the annex as v3 with its detached Ed25519 signature (signed since 3.31; it read v1, unsigned). (64) K-TCD EXPOSURE SNAPSHOT METHODOLOGY (section 6f, new paragraph; portal statements; Member Portal Guide 1.5; Member Technical Handbook 2.15 section 10a). Section 6f documents the methodology of the K-TCD exposure snapshot the monthly annex has carried since revision 3.31: the pool model, RC, PFE, EV, alpha 1.2 (IFR Article 26) and CVA 1.5 (Article 32) applied uniformly to every netting set and both collateral views, the pro rata risk-factor decomposition, the supervisory-factor classes per market (crypto perpetuals `other` 0.32, SP500 and NDX100 `equity_index` 0.20, XAU, WTI and BRENT `commodity` 0.18, EUR, GBP and JPY `fx` 0.04) and the view B haircuts (settlement cash 0, EUR 1, every other balance 1), the barrier exclusion and memo column, every one a signed-off row of the venue parameter store resolved at the capture's pinned parameter version. The annex prints, from the first month whose month-end capture was taken on or after the note's effective date, the `# ktcdMethodology` note (revision v2.1) with every figure read from the store at that pin, and the `# ktcd factors,alpha,<value>,cva,<value>` line only when the alpha and CVA rows the snapshot was computed with are signed off; every annex generated at an unsigned or note-less resolution keeps its exact bytes and its detached signature; the venue expresses no view on recognition under the IFR. REST, WebSocket and FIX are unchanged. (65) AUTO-DELEVERAGING STATED IN FULL, DOCUMENTATION only (sections 6b, 7, 10.3 and 10.9; no field added, removed or renamed, no default moved, `/openapi.json` `info.version` stays 2.35.0, the complete code reference stays at 2.34, every request that worked at 3.68 works identically, FIX unchanged). (a) An auto-deleveraging close is fee-free on both legs in both cases, the classic tear-up at the bankruptcy price and the one-step close of a liquidated member's position on an RFQ-mode market at the mark (section 7, Margin model): the venue charges no maker, taker or other fee on an `ADL`-flagged execution, and the only ledger entry it books on either side is the realized profit or loss (kind `ADLSETTLEMENT`); the venue has never charged one (pinned by test on both paths). (b) Section 7 publishes the ranking behind `adlQuintile`, served on `GET /v1/adl/indicator`, `GET /v1/positions` and the WS `adl` channel unchanged since the sandbox opened (only the quintile was documented before): unrealized profit ratio multiplied by account leverage, highest first, ties by ascending member id, losing positions below every profitable one and reached only by the terminal backstop, the quintile formula, recomputed every 5 seconds and not on a stale mark; the route row and the margin-model summary cross-reference it. (c) Sections 7, 6b and 10.9 state how auto-deleveraging draws candidates: one at a time in ranking order, each selected member's position closed up to the quantity still needed before the next candidate is touched, and within that member pro rata across its same-direction end clients (unchanged: section 10 `ADL` frames, section 6e lot closures); auto-deleveraging reaches you only if you hold the opposite side of the defaulter's market, profitable positions are closed first, and only where they cannot absorb the whole position does the venue close the remaining opposite-side positions, including positions not in profit, at the same bankruptcy price (on an RFQ market the same ranked close is the liquidation step itself, at the mark price); the `DELEVERAGED` role of section 10.9 now reads "your opposite-side position (profitable positions first)". (d) An auto-deleveraging close is credited to the counterparty final and immediate at the close-out price and nothing is withheld against the defaulting member's negative balance, which the venue claims separately (the residual books to the venue, revision 3.39); the ledger kind `RECOVERYDISTRIBUTION`, served since revision 3.30 and named only in that changelog row, is now in section 7 and on the `wallet` channel row with its basis: pro rata by the notional closed against you at the close-out price, capped in aggregate at the participants' loss against the mark, `refId` = the episode id. The venue has applied every one of these rules since genesis. Companion documents: Member Technical Handbook revision 2.15 (section 6.1 and section 8 item 3), Netting and Unsolicited Flow Statement Version 1.4 (section 3 item 3), the Trading rules, REST and Reference pages of the documentation site. (66) EXECUTIONS NAME YOUR OWN SIDE ONLY, DOCUMENTATION only (sections 6 and 10.3; no wire change, no field added, removed or renamed, no default moved, `/openapi.json` `info.version` stays 2.35.0 because the served document is unchanged). Section 6 now states what the venue has served since revision 1: an execution record names your own side only, on `GET /v1/executions`, the WS `execution` channel, the FIX 35=8 trade leg (no PartyID or ContraBroker group) and the event stream, and the `GET /v1/unwinds` defaulter view carries no counterparty identity; identity is disclosed only where Applicable Laws or the Rules provide for it. Companion document: Contract Specifications, Fixed Yield Contracts, Section 9.4 (identification of the Writer of a DEFAULTED Contract to the Taker on written request). Pinned by tests on every surface named. (67) `contractMultiplier` DESCRIBED, DOCUMENTATION only (sections 2a, 5 and 6b; no wire change, no field added, removed or renamed, no default moves, every request that worked at 3.68 works identically). `contractMultiplier`, served on every `GET /v1/instruments` row since revision 3.0, is now described: the contract value, `"1"` on every listed market and FIXED at 1 on BARRIER markets as a listing rule of the kind and a term of the Contract Specifications, Barrier Contracts (Downloads); a barrier listing or update with another value is refused by the venue. The "contract value" of the section 6b formulae is that field. Companion documents: Member Technical Handbook (revision 2.15, section 6.3), the barrier contract specification. (68) FUNDING ON A STALE MARK WAITS (section 7 GRID paragraph and the section 10.9 broker note; decision DOC04-1; the wire twin is item (5)'s `settledTs`). ADDITIVE on every surface: no field is removed or renamed, no default moves. A GRID funding anchor falling due while the market is in reduce-only for `STALE_MARK_PRICE`, or while its last mark is older than the market's staleness threshold, is settled at the first settlement after the market leaves reduce-only, at most 8 per settlement, each at the then rate in force and last sequenced mark on the positions held at that settlement, with the anchor as `settleTs` and the settlement time as `settledTs` on `GET /v1/funding/history`; previously it settled at the last sequenced mark whether or not the market was stale. The catch-up after a venue outage longer than the staleness threshold waits for the mark feed the same way, and the catch-up fence of item (42) stays up until it lands. LOCAL_DAILY is unchanged. FIX is unchanged. Dormant on the sandbox until the operator decision batch epoch announced by venue operations; a production venue applies it from its first day. Companion documents: the trading and REST pages of the documentation site. (69) FUNDING RATE BAND (section 7 and the `GET /v1/instruments` row of section 5; DOCUMENTATION of a standing venue-side limit, no wire change, no new field, no default moves): `funding.rateCap` on `GET /v1/instruments` (and WS `get.instruments`) never exceeds 1 (100 percent per settlement interval) and `funding.rateFloor` never lies below -1; the venue refuses to list or reconfigure a perpetual outside that band. The per-market caps in force (0.01 per 8 h on the crypto perpetuals, 0.005 per day on the equity index, gold, oil and FX perpetuals) are unchanged and remain Trading Parameters under Contract Specifications 7.1; this is a standing venue-side limit on how far they can ever be amended. (70) THE SANDBOX FIXED RATE STATED (sections 1a and 7; the wording behind items (25) and (29), DOCUMENTATION only, no field added, removed or renamed, no default moves): since the date venue operations announce, the five crypto GRID perpetuals on the sandbox (`BTC-USD-PERP`, `ETH-USD-PERP`, `SOL-USD-PERP`, `BNB-USD-PERP`, `XRP-USD-PERP`) publish `funding.rateSource` `FIXED` with `funding.fixedRate` `0.0001` on `GET /v1/instruments`: the venue settles the published fixed rate at every 8 hour anchor, and the rate in force and the predicted rate on `GET /v1/funding/current`, `GET /v1/ticker`, the WebSocket `get.funding` request and the `funding.{symbol}` frames read it. The settled figure is unchanged: +0.0001 per interval was already the rate published for the sandbox (section 1a), so no member's funding payment moves. Section 1a re-describes the sandbox temporary-rate service as a temporary change of the published fixed rate by venue operations, inside the market's `rateFloor` to `rateCap`, announced and reverted the same way; section 7 names the rate in force on a `FIXED` market (the published `fixedRate`, else the last sequenced tick rate). Companion documents: Contract Specifications, USD-Settled Perpetual Contracts, sections 7.1 and 9.2. (71) BRENT FUNDING SCHEDULE (section 7, the LOCAL_DAILY schedules; decision ADR18-A2; a VALUE change only: no field is added, removed or renamed, the `funding` block schema is unchanged and `/openapi.json` `info.version` stays 2.35.0). From the date venue operations announce under Rules section 43, `BRENT-USD-PERP` settles its daily funding on its own row, `zone` `LONDON`, `snapshotLocalTime` `19:30`, `vwapWindow` `19:25-19:30`, five snapshots (the ICE Futures Europe settlement window), instead of the WTI row (New York 14:30, 14:25 to 14:30, five). New York 14:30 and London 19:30 are the same instant for most of the year; in the daylight-saving mismatch weeks of March and of October to November the Brent settlement therefore moves one hour later than before. `WTI-USD-PERP` is unchanged. `GET /v1/instruments` and `GET /v1/funding/current` publish the row in force (`nextSnapshotTs`, `nextSettlementTs`); read it rather than hard-coding a time. No routes, channels or codes change; FIX is unchanged. Companion documents: the REST page of the documentation site. (72) THE LEVERAGE RESPONSIBILITY PARAGRAPH (section 6; entry CONF-376; DOCUMENTATION only, no wire change: no field added, removed or renamed, no default moves, `/openapi.json` unchanged, every request that worked at 3.68 works identically): a stricter margin profile (`imMultiplier` on `GET /v1/account`) is never a bilateral commercial term; it is applied only as a published class of the Rules annex or as a Rules measure recorded with an objective ground and a case reference and notified to you with the section 36 appeal route (the ground, case reference and notice exist since 3.61; the phrase "unless agreed otherwise" was stale). Companion documents: the Trading page of the documentation site. (73) THE 2026-09-11 CODE AUDIT, ENGINE FINDINGS (findings F-445, F-447, F-448, F-451, F-452, F-454, F-456 and F-469; `/openapi.json` unchanged at 2.35.0, no field added, removed or renamed, no default moves, every request that worked before works identically): (a) MARKET ACCESS AND THE BARRIER CLOSE (section 7, code row 3009): the quote of the WRITER on a barrier close request and the acceptance of the holder stay admitted like the request itself, because a close settles existing risk; a writer whose access to the market is `DENIED` (or not admitted on an `ADMISSION` market) can therefore still quote and settle the early close of its own contract, its quote on an OPENING request stays 3009, and a member that is not the writer still answers 2024 on a close request. The engine change rides the decision batch epoch, so it applies to commands no deployed build ever applied; until then the quote answers 3009 and the acceptance of a standing quote 2025, the deployed behaviour. (b) A SWAP TAKE `clOrdId` IS A DUPLICATE-DETECTION REFERENCE ONLY (section 6h): it was registered under the number of the opened contract, which the cancel routes read as an order id, so `DELETE /v1/orders?clOrdId=` and FIX 35=F with `OrigClOrdID` naming a take could reach an unrelated resting order of yours carrying that number; a take reference on the cancel routes now answers 2014 (FIX an OrderCancelReject, `Text` "unknown order") and a `clOrdId` resolves only to an OPEN order of yours carrying it; no shape changes. (c) SCHEDULED SECURITY MARKETS (section 6j): the halt deferral of 3.69 covers a `REDUCE_ONLY` or `POST_ONLY` session; a schedule change or removal by venue operations while the market is in a call or closed moves it straight into `CONTINUOUS` with the phase frame (`nextPhaseTs` `0` on a removal, `auction` then absent from `GET /v1/instruments`) and any crossing orders matched through ordinary continuous trading in time priority (no `AUCTION` flag, no uncross frame, `lastAuctionPrice` untouched), never while the market is halted; the uncross itself is now linear in the number of price levels (no member-visible change). (d) TWO NEW `cancelReason` OUTCOMES OF THE ENTRY INTO CONTINUOUS TRADING on a scheduled SECURITY market (section 6j; an ADDITIVE value on the open `cancelReason` set of the REST order read, the WebSocket `order` frame and FIX `Text(58)`, KronosX-API-Codes section 5.1): `POST_ONLY_WOULD_CROSS`, a `POST_ONLY` order that rested through a call and would cross on its re-entry is canceled rather than rejected (an acknowledged order never rejects), and, when a re-entry print trips the circuit breaker, a remaining order that would cross is canceled `INSTRUMENT_HALT` (an existing value) while one that would not rests. (74) QUANTITY CAPS ON NOTIONAL-CAPPED MARKETS (section 7; `/openapi.json` `AccountParametersWrite` and the `PUT /v1/account/parameters` descriptions and examples, inside this revision's 2.35.0; code audit 2026-09-11): a `maxOrderQty` value written on a market that carries `maxOrderNotional`, or a `maxPositionQty` value on one that carries `maxPositionNotional`, can bind nothing (the engine reads the notional arm there, as section 2a says) and is now refused with `5003` and `... carries a notional order cap (maxOrderNotional applies there): set maxOrderNotional instead` (the position twin likewise), where it was accepted and shown as in force before; on the read the quantity row of such a market is the market's own cap with source `MARKET` and every value stored earlier reads under `inert`. The worked example and the `/openapi.json` examples use `maxOrderNotional` on BTC-USD-PERP, a market that carries the notional caps. No field, status or default moves; a request naming a quantity cap on a notional-capped market is the only one that answers differently, and it never bound. (75) RETENTION FLOOR (section 6d; the venue-side day-count and the paragraph, no wire change; code audit 2026-09-11): the identity is kept THROUGH the fifth anniversary day of the reported end and may be pseudonymised from the day after (Regulation 1182/71 Article 3(2)(c)). Companion documents: the REST and Trading pages of the documentation site, the FIX gateway guide, the API codes reference and the code reference. (76) THE ARTICLE 3(4) MONTHLY AVERAGE (section 6i, `GET /chain/v1/valuation`; code audit 2026-09-11, F-510; ADDITIVE, `/openapi.json` description text only, `info.version` unchanged, every request that worked at 3.68 works identically): the public record gains `monthlyAverageEur` and `monthlyAverageMonth` (the previous completed calendar month's Article 3(4) monthly average aggregate market value that governs the admission cap and the transition figure for the current month; `null` before the first completed month) and `dailyAggregateReachesTransition` (a one-day spike of the day's aggregate above the transition figure, reported and never acted on), `valuationRule` states the rule; `TRANSITION` now follows the monthly average alone and `ADMISSION_CAP` the monthly average or the day's aggregate, as Regulation (EU) 2022/858 Article 3(4) prescribes, where revision 3.68 read both off the day's aggregate. (77) SWAP RECEIVABLES MEMO HEADER (section 6f, the monthly annex; code audit 2026-09-11, F-512; presentation text only, no field, no calculation): the header line of the K-TCD swap receivables memo reads "bilateral swap contracts between the two Members concerned, outside the netting sets" in place of a Membership Pool novation phrase, neutral under both legal bases; the section renders only for rows captured from this release on, so no production annex carries the earlier wording, and an annex of the development venue regenerated from a row captured under the earlier wording renders the new header (the header is not stored). (78) SECTION 6b NAMES `kind` `SECURITY` (DOCUMENTATION only, no wire change, code audit F-118, 2026-09-11): the section 6b sentence on the kinds that trade by RFQ names the fifth kind `SECURITY` (prefunded delivery exactly like `SPOT`, section 6i) beside the four it listed; the opening paragraph and the `GET /v1/instruments` row of section 5 already do, so the section 5 update the 3.66 row announced is complete. The `/openapi.json` `kind` description names the fifth value its enum has carried since 2.32.0 (`info.version` unchanged). Companion documents: the REST page of the documentation site (its instrument field table and its RFQ section). (79) THE ACCEPTED UPLOAD FORMATS AND THE REBUILT COPY OF ONBOARDING DOCUMENTS (section 6g; DOCUMENTATION only, no wire change, Member Portal Guide 1.6 section 12, code audit F-154, 2026-09-11): the Onboarding paragraph names the per-member quota (30 files, 64 MB), the accepted document formats (PDF, PNG, JPEG, modern Office documents, plain text), the refusals (legacy Office files, SVG, archives, macro-enabled Office files, encrypted PDFs) and the rule that the venue stores a REBUILT copy of every accepted file with metadata and active content removed, so a document downloaded from the portal can differ byte for byte from the upload while showing the same content. (80) THE PORTAL ORDER TICKET ON SECURITY MARKETS (section 6g; DOCUMENTATION only, no `/v1` change, code audit F-322, 2026-09-11): section 6g no longer states that the member web portal places no book orders. Since 3.66 the portal's DLT Market tab (Member Portal Guide section 10a) enters LIMIT orders on SECURITY markets only, as the same command `POST /v1/orders` sends, under the same Trader ID (2021), capacity (2033), DEA (3010, 3011), order-to-trade (1004) and rate-limit (1003) rules as the API; every other kind trades through the API only, and the channel sentence of item (59) reads with that qualification (corrected in place at the F-090 recheck of 2026-09-12; until then item (59) still said the portal places no book orders). `/openapi.json` unchanged.DLT MARKET, THE 11 SEPTEMBER CODE AUDIT (section 6i; `/openapi.json` `info.version` stays 2.35.0 with the descriptions updated; code reference revision 2.34 gains the `2049` row; items (81) to (88), the chain chunks of that audit, follow the batch's items (73) to (80)): (81) NEW code `2049` TOKEN_RESTRICTED, answered before the engine on order entry, amends, RFQ requests and quotes for a SECURITY market whose instrument is frozen on the Kronos X chain or for a client whose custody wallet there is frozen by the venue registrar; a wallet freeze also cancels the client's resting orders on SECURITY markets (`ADMIN_CANCEL`) and an instrument freeze halts its markets (the new "Freezes" paragraph). (82) `POST /v1/chain/instructions`: a `WalletRegister` must name YOUR member id as sponsor (`5003` otherwise) and the venue registrar co-signs it on your submission, which is your attestation of `professional`; an envelope already final answers `5003` `DuplicateInstruction` with its `Final` status and is never applied twice; a body the chain refused at an earlier block may be posted again. (83) The canonical body is SCHEMA 2: the two settlement client uids and the registration's `clientUid` left the layout, `house` and `clientRef` took the registration's place, so no client identifier is in any block byte; a custody wallet id is the venue's keyed derivation, read from the NEW `walletId` of `POST /v1/chain/wallets/custody` and from `GET /v1/chain/wallets` (rows gain `house` and `clientRef`), never computed from a member number and a client identifier. (84) The public record: a frozen instrument reads `status` `Frozen` with the NEW `frozenBy` (`REGISTRAR` or `ISSUER`; an issuer can neither lift nor replace the registrar's freeze), and a registrar action carries no `ground` and no `caseReference` on `/chain/v1/tx/{hash}` or in a block view (they were served there before this revision). (85) DLT Market, the 2026-09-11 audit, chain chunk 2 (section 6i): a fill delivers, after your house wallet, from the external wallets you sponsor for yourself (`houseCoveredUnits` covers both); a split uplifts your engine balance for the LOCKED units of the external wallets you sponsor exactly like your custody wallets; a `tsNanos` ahead of the venue clock beyond a small allowance is refused, and no participant stamp ever sets a block time; the public record judges every historical block against the validator set that signed it, so a later validator replacement never turns a final block into a pending one; the balance proof and integrity reads of `/chain/v1/*` weigh more of the public rate budget and at most two are served at once (`429` with `Retry-After` beyond). (86) DLT Market, the 2026-09-11 audit, chain chunk 3 (section 6i): the finality promise is kept by the chain rules across validator set changes (no set that has a validator not operated by PM MTF Ltd loses its last one, the operator's validators stay below the quorum of such a set, a seat's key or status changes only with the seat holder's own signature); the `registrarSignatures` list of a signed envelope is bounded before any signature is checked (at most one entry per registrar key, `keyIndex` 0 to 255); an instruction whose engine credit would not fit is refused with `size overflow` instead of failing the block; a block the venue's own signer cannot sign in time is held and retried unchanged, never dropped, and movements out of trading answer `3012` while it is held; on a venue cluster an instruction a node could not build before it stopped leading reads `Rejected` with a reason naming the leader and a signed envelope posted to a node that is not the leader answers `5030` (`503` on the public route), so you resubmit to the leader; a chain refusal after an engine debit re-credits you, and a re-credit the engine did not accept is retried by the venue at its next start. No field is added or removed. (87) DLT Market, the 2026-09-11 audit, chain chunk 4 (section 6i): `/chain/v1/head` and `/chain/v1/validators` gain `externalValidators` (the validators of the set in force not operated by PM MTF Ltd) beside `requireExternal`, and the head gains `headValidators` and `headQuorum`, the size and the quorum of the set that signed the head block (`headSignatures` counts against that set; `validators` and `quorum` describe the next block); all additive. A hash-taking public route answers its coded `400` (`must be 64 hex digits`) on 64 non-hex characters, and `POST /v1/chain/unlock` and `POST /v1/chain/transfers` answer `2013` on a malformed wallet id, where the venue answered `5000` before. A split's uplift of your engine balance is measured on the state the block applies the split to (a distribution or a transfer out of yours in the same block is counted) and arrives as one credit per member; a venue restart keeps the ISIN on every settlement row of `GET /v1/chain/settlements`. No field is removed. (88) DLT Market, the 2026-09-11 audit, chain chunk 5 (section 6i): a settlement obligation survives a venue restart in every case: at every start the venue re-derives from its own transaction record any fill on a SECURITY market that its ledger intake had no durable record of, so a fill acknowledged in the moment before a stop or a crash still settles in the first blocks after it, exactly once. `POST /v1/chain/transfers`, `POST /v1/chain/unlock` and a redemption of a custody holding are checked against the chain's own rules BEFORE your engine balance is debited: a refusal the chain can decide on its committed state (a frozen party, the instrument's transfer rules, a lock-up, the client wallet's holding) answers `5003` with `chain.reason` and never touches your balance; a refusal at the block re-credits you FIRST and answers `5003` only after that re-credit was attempted (a re-credit the engine did not accept is on the venue's reconciliation view and retried at its next start, as before). The finality rule of section 6i holds whatever the validator set's composition: a block is never final without a signature from a validator not operated by PM MTF Ltd unless the venue explicitly runs without that requirement, which `GET /chain/v1/head` reports as `requireExternal` false. No field is added or removed; no request changes. (89) THE ARTICLE 3(4) MONTHLY AVERAGE (section 6i, "The Article 3 figures"; ADDITIVE, `/openapi.json` `info.version` unchanged at 2.35.0, description only; code audit 2026-09-11, F-152): `GET /chain/v1/valuation` publishes `monthlyAverage` (the frozen Article 3(4) average of the last complete calendar month: `month`, `days`, `aggregateEur`, `status`, `perIsin[]`) and `monthlyAverageRule`; `null` with the note while fewer than one complete month is recorded. The operator refuses an admission when either the live aggregate at the moment of admission or that average would reach the EUR 6 billion cap and decides the transition strategy on the average; no request changes and nothing changes your trading. (90) THE DLT MARKET WRITE ROUTES, corrected (section 6i, the route table and the Codes paragraph; `/openapi.json` descriptions only; code audit 2026-09-11, F-159): every refusal of `POST /v1/chain/transfers` and `POST /v1/chain/unlock` is `5003` with `chain` (`state` `Rejected`, `reason`), the venue's pre-flight refusals included (revisions 3.66 to 3.69 answered those as `5003` with `error` and no `chain` object); an engine refusal of the debit is `3001` INSUFFICIENT_MARGIN in the standard envelope, never the `3004` the earlier text named; `POST /v1/chain/instructions` answers `2013` with `msg` on an unreadable body and `2013` with `error` naming the field on an envelope of unknown kind, auth, wallet class or action kind. Code audit 2026-09-11 (the gateway batch): `PUT /v1/clients/{clientUid}/identity` refuses with `2030` a `nationalId` that contains a control or formatting character, starts or ends with whitespace, or starts with `=`, `+`, `-` or `@`, on both Annex II rollout stages (section 6d; the value is filed verbatim as the position holder identifier of the venue's Art 58 report); a `+` or `-` inside the value stays accepted. Code audit 2026-09-11 (the gateway batch, second tranche): (i) a call-auction execution credits both parties' order-to-trade rings on that market like a continuous execution (section 4 and `markets[].otr.trades` in section 7; revisions 3.67 to 3.69 counted only continuous executions, so an auction participant's ring read its order messages against no trades); (ii) `account.fees.pending[]` lists a dated `INSTRUMENT` change for a member WITH a tier exactly when the announced rates move what the member pays on that market under the per-side lower-rate rule (the earlier text listed those rows only without a tier; section 7 states the rule); (iii) the two public candle routes `GET /v1/klines` and `GET /v1/mark-klines` answer HTTP 429 with code `1003` and `retryAfterMs` beyond a per-peer window or the venue-wide concurrency budget of candle aggregates (section 4 and section 5; `/openapi.json` gains the `429` response on both; your member buckets are not involved), the `1d` mark series is served from the venue's derived daily bars plus a live fold of the current day with the same rows and shape. Every route, status, field and default of revision 3.69 is unchanged. Code audit 2026-09-11 (the same revision): `PUT /v1/account/parameters` and `PUT /v1/account/stp` are metered on the READ bucket (reference data, not order entry, exactly like `PUT /v1/clients/{clientUid}/identity`; they consumed neither bucket before), so `1003` with `retryAfterMs` answers them once your read budget is spent and order entry is untouched; a refused self-service write echoes at most the parameter names attempted, and a body with more than 15 entries in `set` or `clear` or a name over 64 characters is refused with a fixed sentence (`5003`) before anything is read. The DLT routes of section 6i are typed in `/openapi.json` 2.35.0 as served (request schemas `ChainCustodyWalletRequest`, `ChainTransferRequest`, `ChainUnlockRequest`, `ChainInstructionRequest` with named examples; answers `ChainInstructionResult`, `ChainWalletsResult`, `ChainSettlement` as a bare array, `ChainHead`), documentation only. Code audit 2026-09-11 (the gateway batch, third tranche): (iv) `PUT /v1/market-segments` and `PUT /v1/eligibility` (with or without `?symbol=`) answer `2013` on a body whose `allow` is an explicit JSON `null` (the body must carry an array, empty or not; an absent `allow` still reads as an empty list), exactly like the WebSocket `marketSegments.set` twin; the `/openapi.json` 400 description of the route names it (`info.version` unchanged at 2.35.0); (v) the member web portal's DLT Market order ticket shares your ONE order-entry bucket (`1003`) and counts under the order-to-trade regime of the market (`1004`) like `POST /v1/orders`, and its cancel counts as an order message like `DELETE /v1/orders/{orderId}` (sections 4 and 6g; no `/v1` route changes); (vi) section 2a states that the 1,000 JPY unit of the two JPY markets applies from the re-listing date venue operations announce for your venue, a venue still serving the earlier 1 JPY listing publishing `tickSize` `0.000001` on the two rows until then (documentation only; the listing values in force are always the ones on `GET /v1/instruments`). Code audit 2026-09-11 (the chain follow-ups, gateway and chain host only, nothing journaled, no epoch): (91) `POST /v1/rfq/{rfqId}/accept` (and the portal accept) answers `2049` on a SECURITY market when the instrument, the request's client wallet or the quote's client wallet is frozen on the Kronos X chain, the same gate as the request and the quote, answered before the engine; the portal quote answers `2049` like the REST quote; a registrar freeze of a client custody wallet also cancels that client's open RFQ requests and live quotes on SECURITY markets (the requests read canceled, the quotes are withdrawn, exactly like the resting orders); section 6i, the `2049` row and the accept route's `/openapi.json` 400 description (`info.version` unchanged at 2.35.0); (92) the public record: `valuation` is served from the venue's last evaluation (the 60 s sweep's, or a refresh at most one second old); a transaction or wallet URL of the explorer whose value is not 64 hexadecimal digits shows the explorer's own not-found page, never its unavailable page; the block and transaction reads memoise each stored block once per height (no wire change); an engine-first movement whose debit the venue could not confirm (a leader change while it was in flight) answers `5000` INTERNAL_ERROR with a sentence naming the ledger note `kxc:<txHash>` to compare before resubmitting, nothing queued on the chain in that case (section 6i). Code audit 2026-09-11 (the gateway follow-ups, gateway only, nothing journaled, no epoch): (93) section 7 states when a dated `INSTRUMENT` change is listed under `account.fees.pending[]` for a member WITH a tier (exactly when the announced rates move the lower of the tier rate and the market rate on at least one side; a market a programme prices you on is never listed, nor one where the tier stays the lower rate on both sides before and after the change), the rule the venue has applied since revision 3.70 and the `/openapi.json` `kind` description now words the same way (documentation only); (94) the `ChainInstructionRequest` schema of `/openapi.json` declares `clientRef` (the custody wallet id of your client, 64 lowercase hexadecimal characters, WalletRegister only, optional) in place of the `clientUid` the served envelope never bound (schema 2 of section 6i left it; a generated client posting it changed nothing), `info.version` unchanged at 2.35.0; no route, status, field or default of revision 3.69 moves. Code audit 2026-09-11, the projections follow-ups (2026-09-12, gateway only, nothing journaled, no epoch): (95) section 6i states the ONE Article 3(4) arithmetic behind `monthlyAverage` (per instrument the mean of its daily EUR closing prices over the days of the month it was priced times the units recorded on its last day, an instrument unpriced all month at its declared basis; `perIsin[].averageEur` is that contribution and `aggregateEur` their sum, the figure the admission refusal projects from), and the `monthlyAverageRule` sentence of `GET /chain/v1/valuation` now describes that arithmetic instead of "the average of the daily closing figures", which was a second reading the first build carried beside it; no field, status or default moves and `info.version` stays 2.35.0. |
| 3.69 | 2026-09-11 | DLT MARKET VERIFICATION (section 6i; `/openapi.json` `info.version` unchanged at 2.34.0; code reference unchanged at 2.33). A member that holds only TRADE credentials and calls the chain routes within its order budget sees nothing new. (1) The four chain writes (`POST /v1/chain/wallets/custody`, `/v1/chain/transfers`, `/v1/chain/unlock`, `/v1/chain/instructions`) now apply the credential gate the routes table always documented: a READ_ONLY credential answers `1005` KEY_NOT_PERMITTED before anything happens (previously the write went through), and each call charges the ORDER rate bucket (`1003` with `retryAfterMs` when exhausted). (2) Codes `3012` LEDGER_FINALITY_PENDING and `5006` LEDGER_UNAVAILABLE carry their sentence in `msg` (previously the generic "invalid request"); the HTTP statuses stay 400 and 503 as published in the code reference. (3) A `units` figure beyond what the ledger can represent on `transfers` or `unlock` answers `2002` (previously a transport error). (4) DOCUMENTATION corrections, no wire change: the introduction and section 5 name `SECURITY` in `kind` and section 5 lists the `auction`, `tradingPhase`, `lastAuctionPrice` and `lastAuctionTs` fields (served since 3.66 and 3.67, described only in sections 6i and 6j until now); section 9 gains the quick-reference rows `2046`, `3012` and `5006`; section 10.2 describes the phase and uncross payloads of `status.{symbol}`; the `5006` sentence in 6i and in section 9 now says what the venue does (the code is answered only by a chain host running without its engine bridge; a venue that does not run the chain does not serve the chain routes, HTTP 404). (5) Section 6i gains "Signing an instruction: the canonical encoding", the complete language-neutral layout a signer of `POST /v1/chain/instructions` needs (field order, widths and byte order, string, bytes and hash encodings, the enumeration values, the `"KXC:instr:"` digest, DER ECDSA P-256, the `"KXC:tx:"` transaction hash, the wallet id derivation), replacing the earlier reference to an SDK encoder; an SDK package remains a follow-up and is not needed to use the route. (6) BEHAVIOUR of scheduled SECURITY markets after the 11 September audit (ADR-22 section 9): a due uncross on a halted market is deferred, the phase stays in the call and the uncross runs when the halt lifts; when a call ends with orders still crossing (the collar excluded every candidate, a same-member or ineligible pair), the entry into continuous trading replays those orders in time priority through ordinary matching (earlier order rests, later order takes at the resting price, ordinary fees, no `AUCTION` flag), so the book is never crossed in continuous trading; the collar reference is the last trade (an auction print updates it) else the mark. Companion documents: the code reference and the REST page of the documentation site. |
| 3.68 | 2026-09-10 | THE ARTICLE 3 FIGURES OF THE DLT MARKET (section 6i; `/openapi.json` `info.version` 2.33.0 to 2.34.0; code reference unchanged at 2.33). ADDITIVE and public: no private route changes. (1) NEW public record `GET /chain/v1/valuation` (no authentication; also the explorer's Valuation page): `status` (`OK`, `WATCH`, `ADMISSION_CAP`, `TRANSITION`), `aggregateEur`, the `thresholds` of Regulation (EU) 2022/858 Article 3 (EUR 500 million issuer market capitalisation per share, EUR 1 billion issue size per bond or money-market instrument, EUR 500 million assets under management per fund unit, EUR 6 billion aggregate admission cap, EUR 9 billion transition figure), the ECB USD per EUR rate in use, and per instrument the circulating units, the price with its source (`AUCTION` = the closing or opening auction price of section 6j, else `LAST_TRADE`, else `MARK`), the market value in EUR, the declared basis and the class cap. (2) The venue admits a new DLT financial instrument only with a recorded Article 3 declaration inside its class cap and with room under the aggregate cap, and refuses a further issue that would reach the class cap or the transition figure; these are operator controls and change nothing on your order entry. Companion documents: the reference and REST pages of the documentation site. |
| 3.67 | 2026-09-10 | CALL AUCTIONS ON SECURITY MARKETS (new section 6j; sections 5, 9 and 10; `/openapi.json` `info.version` 2.32.0 to 2.33.0; complete code reference revision 2.33). ADDITIVE on every surface: no field is removed or renamed, no default moves, and a member that reads nothing new sees nothing new; a SECURITY market without a schedule trades exactly as at 3.66. (1) `GET /v1/instruments` gains `auction` (`schedule` LOCAL_DAILY, `zone`, `openCallStart`, `openUncross`, `closeCallStart`, `closeUncross`), `tradingPhase` (`CONTINUOUS`, `CALL_OPEN`, `CALL_CLOSE`, `CLOSED`), `lastAuctionPrice` and `lastAuctionTs` on scheduled SECURITY markets; absent elsewhere. (2) During a call or while closed, limit orders rest without matching and IOC, FOK, MARKET and conditional orders reject the NEW code `2046` AUCTION_PHASE; the book may cross until the uncross. (3) The uncross prints one price for every crossing order (max executable volume, then minimum surplus, then closeness to the reference, then the lower price; the collar is the market's `priceBandBps`); its trades carry the NEW `flags` value `AUCTION` on REST, WS and the projections and charge the maker rate on both sides. (4) The public `status.{symbol}` channel carries `{ tradingPhase, nextPhaseTs }` on each phase change and `{ auction: { price, qty, surplusQty, surplusSide } }` on each uncross. (5) The closing auction price is the DLT Market's daily valuation. FIX is unchanged (an auction print is an ordinary execution report). Companion documents: complete code reference revision 2.33, the reference and REST pages of the documentation site. |
| 3.66 | 2026-09-10 | THE DLT MARKET (new section 6i; sections 5 and 9; `/openapi.json` `info.version` 2.31.0 to 2.32.0; complete code reference revision 2.32). ADDITIVE on every surface: no field is removed or renamed, no default moves, and a member that reads nothing new sees nothing new. PM MTF Ltd operates a DLT trading and settlement system under Regulation (EU) 2022/858; its instruments settle on the Kronos X chain, the venue's permissioned ledger. (1) Instrument `kind` gains the value `SECURITY` (a DLT financial instrument: shares, bonds, money-market instruments, fund units); its `baseAsset` is the instrument's ISIN and equals `isin`; a SECURITY market behaves exactly as section 6a describes SPOT (prefunded, no positions, no margin, no funding, no `reduceOnly`, `unitScale` above 1 for fractional shares) on the book or by RFQ. (2) Settlement: every fill creates a settlement obligation the chain settles in a block that is FINAL at two thirds of the validator set including one external validator, typically within two seconds; `GET /v1/chain/settlements` lists yours with `state` `Pending`, `Settled` or `Failed`. (3) NEW private routes `GET /v1/chain/wallets` (your custody wallets per client and sponsored external wallets with holdings), `POST /v1/chain/wallets/custody`, `POST /v1/chain/transfers` (engine debited first, chain refusal re-credits and answers `5003` with `chain.reason`), `POST /v1/chain/unlock`, `POST /v1/chain/instructions` (an external wallet's own P-256 signed instruction) and `GET /v1/chain/settlements`. (4) NEW public record `/chain/v1/*` (blocks, transactions with Merkle inclusion proofs, instruments with the integrity of issue, wallets with balance proofs, validators, integrity, settlements) and the explorer at `https://explorer.perpetuals.com`; wallets are key hashes and the public record carries no member or client identity. (5) NEW codes `3012` LEDGER_FINALITY_PENDING (a movement out of trading refused while the validator quorum is not reached; trading itself continues) and `5006` LEDGER_UNAVAILABLE. FIX is unchanged (a SECURITY market is an ordinary order-entry symbol). Companion documents: complete code reference revision 2.32, the reference and REST pages of the documentation site. |
| 3.65 | 2026-09-10 | VERIFICATION revision of ADR-20 (sections 4, 6h, 8 and 9; `/openapi.json` `info.version` 2.30.0 to 2.31.0; complete code reference revision 2.31). ADDITIVE on every surface: no field is removed or renamed, no default moves, and a member that reads nothing new sees nothing new. (1) `cancelReason` `MARKET_ACCESS` (complete code reference section 5.1, VENUE-INITIATED): a working order the venue cancels on a `DENIED` access decision or on an admission flip named its reason `NONE` on the order history, the WS `order` channel and the FIX report `Text(58)`; it now names `MARKET_ACCESS` on all three (the cancel itself is unchanged since 3.63, the string is new). (2) Swap offer validity (section 6h): `swapOfferValiditySec.min` on `GET /v1/instruments` now publishes the rule the venue enforces, 60 s unless the market sets its own (it read 59, the engine's clock-skew floor); an `expiresInMs` below the published minimum rejects `2036` at the gateway before anything is sequenced, an exactly 60 s offer is still admitted. (3) Rate limits and order-to-trade thresholds (section 4): a member-specific value tighter than the class in force, or a move to a lower class, is a Rules measure that venue operations record with an objective ground and a case reference and notify to you by mail with the section 36 appeal route (the values were tighter-only already; the ground, the case reference and the notice are new). (4) `PUT /v1/market-segments` (section 8): the response schema in `/openapi.json` lists `symbol` beside `version` (served since 3.63). (5) The complete code reference section 5.3 names the private `venueEvents` type `MEMBER_PARAMS` of revision 3.61. FIX is unchanged apart from the `Text(58)` value in (1). Companion documents: complete code reference revision 2.31, the reference, REST and FIX pages of the documentation site. |
| 3.64 | 2026-09-10 | YOUR OWN TIGHTER LIMITS (section 7, "Your own tighter limits"; `/openapi.json` `info.version` 2.29.0 to 2.30.0; complete code reference revision 2.30; Member Portal Guide 1.4). ADDITIVE on every surface: no field is removed or renamed, no default moves, and a member that reads nothing new sees nothing new. (1) NEW `PUT /v1/account/parameters` (TRADE): set or clear your own pre-trade limits within the venue's rules (RTS 7 Article 20, RTS 6 Article 15), account-wide (`maxOpenOrders`, `imMultiplier`, `priceBandBps`, `marketMaxSlippageBps`, `maxOpenRfqs`, `maxOpenContracts`) or for one market with `symbol` (those plus `maxOrderNotional`, `maxOrderQty`, `maxPositionNotional`, `maxPositionQty`), body `{symbol?, set, clear}`. The venue applies the TIGHTEST of the market rule, any value venue operations set for your account and your own value: a value of yours can only tighten what is in force, a looser one is refused with `5003` and a sentence naming the rule (the market rule, or the venue's value for your account at the same scope) before anything is written; a cleared name reverts to the venue's rule; venue operations never edit your own values. The answer carries what was written and the effective values afterwards. (2) `GET /v1/account/parameters`: the sources `SELF` and `SELF_MARKET`, documented as reserved since 3.61, are now in force for values you set yourself; NEW `self` `{account, markets: [{symbol, values}]}` lists every value you hold raw. (3) The `MEMBER_PARAMS` frame's `origin` reads `MEMBER` on your own writes. (4) The member portal's "API and limits" page gains the same controls (Member Portal Guide 1.4). Not self-service: `withdrawalBuffer`, `sessionStatus` and `access` (venue measures) and `stp` / `stpScope` (`PUT /v1/account/stp`). FIX is unchanged. Companion documents: complete code reference revision 2.30, the REST and WebSocket pages of the documentation site. |
| 3.63 | 2026-09-10 | PER-MARKET MARKET SEGMENTS, PER-MARKET STP AND MARKET ACCESS IN FORCE (sections 6, 7, 8 and 10; `/openapi.json` `info.version` 2.28.0 to 2.29.0; complete code reference revision 2.29). ADDITIVE on every surface: no field is removed or renamed, no default moves, and a member that reads nothing new sees nothing new. (1) Market Segments per market (section 8): `GET` and `PUT /v1/market-segments?symbol=` read and replace your PER-MARKET layer on one market (its own `version`, `4001` when stale, `2001` for an unknown symbol); the account read gains `markets`, every layer you hold; `GET /v1/market-segments/matchable-members?symbol=` answers the codes you can match with on that market. A layer only ever NARROWS: on a market a counterparty must be admitted by the account configuration of both sides AND by the market layers of both sides, on the book, in FOK counting, in the matchable book, in RFQ and in swap takes alike; the WS twins `get.marketSegments`, `marketSegments.set` and `get.matchableMembers` take the same optional `symbol`, the `eligibility` frame carries `symbol` for a layer change. Venue operations edit a configuration only as a Rules measure (section 35 default management or a section 36 sanction) with a case reference, notified to you by mail with the appeal route; a first write at onboarding records your own instruction. (2) Per-market STP (section 6): `PUT /v1/account/stp` takes an optional `symbol`; the election is then yours for that market alone and wins over the account election there, `INHERIT` removes it, the answer carries `symbol`, `stpSource` and `stpScopeSource`. (3) `GET /v1/account/parameters` (section 7): every market row gains `stpSource` and `stpScopeSource` (`MEMBER` \| `SELF` \| `MEMBER_MARKET` \| `SELF_MARKET`) and `eligibility` `{restricted, source, memberLayer, marketLayer}`, the account block `stpSource` and `stpScopeSource`. (4) Market access (section 6, code `3009`, `accessMode` on `GET /v1/instruments`, `markets[].access` on your parameters read) is now fully in force on every path: a market announced as ADMISSION admits only members holding a stored access decision, a `DENIED` decision cancels your working orders on that market (`cancelReason` `MARKET_ACCESS`, VENUE-INITIATED, named in revision 3.65) and refuses new risk while reduce-only flow stays admitted and open contracts run to term; an existing market is flipped only after a seven Business Day announcement and never while a non-admitted member holds an order, a position or a contract on it. FIX is unchanged. Companion documents: complete code reference revision 2.29, the REST and WebSocket pages of the documentation site. |
| 3.62 | 2026-09-10 | THE CLASS LADDERS, THE DEA CONTROLS, THE MEASURED FEE SCHEDULE AND YOUR OWN STP ELECTION (sections 4, 6, 7 and 9; `/openapi.json` `info.version` 2.27.0 to 2.28.0; complete code reference revision 2.28). ADDITIVE on every surface: no field is removed or renamed, no default moves, and a member that reads nothing new sees nothing new. (1) Rate limits (section 4): every member sits in a PUBLISHED class of the rate-limit ladder (`STANDARD` = the venue defaults, every member without an assignment; `DMA`, `MM` and others as the venue publishes them with their criteria); each class carries the per-minute rates and a per-SECOND burst ceiling on order entry (`ordersPerSecBurst`, enforced beside the per-minute bucket where published, a `1003` with a sub-second `retryAfterMs`; `0` = none published); a member-specific value is only ever tighter than its class and a raise is only ever a class assignment; an existing per-member raise became a class assignment at this release, never a silent cap. `account.rateLimits` gains `readsSource` and `burstSource` beside `source` (`VENUE` \| `CLASS` \| `MEMBER`). (2) Order-to-trade regime (section 4): the window (`windowMinutes` 60) and the binding floor (`minOrders` 100) are published; every member sits in a published OTR class, a member-specific threshold only ever lower; `account.otr` gains `source` and the prepared `surcharge` row `{enabled, bps}` (applied to nobody while `enabled` is false). (3) Direct electronic access controls (section 4, MiFID II Article 48(7)): a DEA-flow class publishes a count and a notional of `reg.dea` orders per member per UTC day; venue operations can stop ONE DEA client on your account by identifier (`clientUid` or `reg.traderId`) as a Rules measure with a ground and a case reference, notified by mail with the section 36 appeal route. NEW codes `3010` DEA_CLIENT_BLOCKED (a `reg.dea` order of a stopped identifier, on REST, the WS ack and the FIX report with `9026=Y` alike, before anything is sequenced; unmarked flow untouched) and `3011` DEA_FLOW_LIMIT (the day's allowance reached; only `reg.dea` orders count). `account.dea` gains `class`, `source` and `usedToday`. (4) Fees (section 7): tier assignment is COMPUTED from the published criteria (the 30-day traded notional band, the role) on a published monthly cycle for every member, never assigned by hand; the Market Maker Scheme tier exists only under a signed agreement; a member-market rate (`PROGRAMME` on the market rows) exists only as an enrolment in a published per-market programme whose measured qualification you meet; every rate change of the schedule carries an effective date at least seven Business Days ahead and never applies before it. `account.fees` gains `volume30d` and `cycle`, `criteria` and `measuredAt` now carry your measurement, and `pending` lists every rate change that will touch you `{kind, name, symbol, makerFeeRate, takerFeeRate, effectiveFrom, effectiveFromIso}`. (5) NEW `PUT /v1/account/stp` (TRADE): your own election of the self-trade prevention policy and scope (section 6), either field alone keeps the other; `GET /v1/account` gains `stp` and `stpScope`. FIX is unchanged (order entry only; `3010` and `3011` ride the standard reject report). Reserved and documented as such: `SELF` / `SELF_MARKET` sources (values you set yourself), per-market STP, the OTR surcharge activation. Companion documents: complete code reference revision 2.28, the REST and reference pages of the documentation site. |
| 3.61 | 2026-09-09 | YOUR EFFECTIVE PARAMETERS and the publication of every rule of the market (sections 4, 5, 7, 9, 10.3 and 10.9; `/openapi.json` `info.version` 2.26.8 to 2.27.0; complete code reference revision 2.27). ADDITIVE on every surface: no field is removed or renamed, no default moves, and a client that reads nothing new sees nothing new. (1) NEW `GET /v1/account/parameters` (READ_ONLY or TRADE): every parameter the venue applies to YOUR account with the scope that supplies each value (`{ value, source }`, `value` written even when null, `source` one of `VENUE`, `MARKET`, `MEMBER`, `MEMBER_MARKET`, `LEGACY`, the fee sources `INSTRUMENT` \| `TIER` \| `PROGRAMME`, an open set), at account level and for every listed market, plus your rate-limit and order-to-trade classes, your fee resolution, your margin-watch level and the historical per-account quantity override under `legacy`; a stored value a tighter scope beats is listed under `inert` on the same row. Only your own values are ever returned. (2) `GET /v1/instruments` (and WS `get.instruments`) now publishes every rule of the market the venue enforces (Rules 13.8, Website AND API): `markJumpRejectBps`, `staleMarkTimeoutSec`, `circuitBreaker` `{bps, windowSec, cooldownSec}`, on PERP rows `liquidation` `{slicePercent, minSliceQty, aggressionBps}` each with a `...Source` (`MARKET` \| `VENUE`), `funding.rateCap` and `funding.rateFloor`, on GRID markets `funding.rateSource` (`TICK` today on every listing; `FIXED` with `funding.fixedRate` where the venue publishes a fixed rate, under which `GET /v1/funding/current` reports that rate as the rate in force and the predicted rate), `maxOpenOrdersPerMember` (omitted where the market carries no per-market ceiling), `accessMode` (`OPEN` on every listing today; `ADMISSION` = only admitted members may open new risk), on BARRIER rows `barrierGuard` `{entryBandBps, minTakeProfitBps, minBarrierBps}` (the section 6b quote guard, 50 / 10 / 10 unless the market sets its own) and `strictTakeProfitMaxLifeHours` (24 unless set), on SWAP rows `swapOfferValiditySec` `{min, max}`; `marketMaxSlippageBps`, served since 2.x, is documented in section 5 (documentation fix). (3) The rule these surfaces make visible: a member-specific value can only ever TIGHTEN a market rule, is written against an objective published class (the Rules annex under 13.8 and 3, published before any value is written) or as a measure with a stated rulebook ground and a case reference notified to you by mail with the section 36 appeal route, and never loosens anything; the venue's own group members are on the same rule. (4) NEW private `venueEvents` type `MEMBER_PARAMS` `{scope, symbol, origin, set[], cleared[]}` on every change of your own values (the names only; re-read the route). (5) NEW code `3009` MARKET_ACCESS: you may not open new risk on a market that requires admission you do not hold, or where your access is `DENIED`; reduce-only flow stays admitted; no listing requires admission today, so no member can receive it before venue operations announce an admission market. Reserved for later revisions and documented as such: `SELF` / `SELF_MARKET` sources (values you set yourself), the rate-limit and order-to-trade class ladder with per-second burst ceilings (`ordersPerSecBurst` reads 0, `class` reads `STANDARD`), measured fee tiers (`fees.criteria`, `measuredAt`, `pending`), the DEA controls (`dea` reads empty). FIX is unchanged. Companion documents: complete code reference revision 2.27, the REST, WebSocket and reference pages of the documentation site. |
| 3.60 | 2026-09-04 | Order status on live reads (section 6, Manage orders; `/openapi.json` `info.version` 2.26.7 to 2.26.8). CORRECTION, no field added, removed or renamed, no default moves, the `status` value set is unchanged: a RESTING order that a later aggressor partially consumed reported `NEW` on the state reads, `GET /v1/orders/open`, `GET /v1/orders/{orderId}`, WS `get.openOrders` and `get.order`, and the FIX `150=I` restatement (`OrdStatus(39)=0`), with `remainingQty` below `qty`; it now reports `PARTIALLY_FILLED` (`39=1`) on all five, the value the event payloads (the `order` channel, the FIX drop-copy fill report, `GET /v1/orders/history`) already derived from the fill and the value an order part filled ON ENTRY always carried. `remainingQty` was correct throughout, so an integration that trusts `remainingQty` sees no change; one that keys on `status == "NEW"` to mean "nothing filled yet" should read `remainingQty` against `qty` until the rule is live. DORMANT on the sandbox until venue operations announce its activation (the rule is gated on the venue's replay-contract epoch, engine sequence 1,000,000,000 on the sandbox, expected within about a day of this revision at its current command rate); production runs it from its first day. No routes, channels or codes change. ALSO IN THIS REVISION, both answering the outside engine findings of 2026-08: (a) DOCUMENTATION, where a rejection lands per transport (section 6, "Where a rejection lands, per transport"; section 10.5; FIX rules of engagement revision 2.19, section 4): REST reports an engine rejection inside HTTP 200 with `code` 0 as `data.status` `REJECTED` plus `data.rejectCode` (the envelope says the request was processed and journaled, the payload is the order's state in the shape the reads serve), the WebSocket ack is the verdict (`success: false`, the code, the same payload in `data`), FIX answers an ExecutionReport `150=8` / `39=8` with the code in `9010`; the convention is deliberate and stays. (b) WIRE, the `2002` versus `2013` shapes (sections 2 and 9, complete code reference revision 2.26): 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) now answers `2013` INVALID_ORDER on every route and transport (order entry, amend, FIX replace, RFQ create, quote, offer and take); it answered `2002` before, so a client could not tell a typo from a grid miss. Zero, negative, off-grid and out-of-range values and a missing required price stay `2002`. Not epoch-gated: the check is the gateway's, before anything is sequenced, so it is live from this revision on every environment. Also, a WS `order.place` ack rejected by a gateway-level check now carries the registry wording in `msg` as documented since revision 2.3 (it said `rejected`). |
| 3.59 | 2026-08-27 | Security remediation batch (one new error code `2045` CLORDID_COLLISION, `/openapi.json` `info.version` 2.26.6 to 2.26.7). ADDITIVE and DORMANT until venue operations announce activation on each environment (production runs the batch from its first day); no field is added, removed or renamed and no default moves. Seven internal safety fixes are gated together on a replay-contract epoch above every deployed head, so a member sees no change until activation. The only member-visible surface is the NEW code `2045`: an order (`POST /v1/orders`, FIX `35=D`) or a swap take (`POST /v1/rfq/{rfqId}/accept` on a SWAP market) whose `clOrdId` hashes to the same 64-bit value as a DIFFERENT `clOrdId` you still have live in the idempotency window is refused rather than displacing the stored reference (astronomically rare; re-send with a different `clOrdId`). The identical-payload retry path (`2009`) is unchanged and remains the deliberate retry mechanism. After activation, additionally: a swap take is idempotent under retry exactly like an order (a true retry of the same `clOrdId` no longer opens a second contract, it answers `2009`); an RFQ create, quote or accept rejects on a stale mark at the same instant an order does; a barrier early-close accept is refused (`2035`) once the contract has reached its `expiryTs`; and a conditional order that trips after your session turned reduce-only is canceled rather than opening exposure. No routes, channels or other codes change; sandbox behaviour before activation is byte-identical to 3.58. |
| 3.58 | 2026-08-26 | Writer risk model (section 6b; one new error code `2044` PREMIUM_SHARE, `/openapi.json` `info.version` 2.26.5 to 2.26.6). ADDITIVE: no default moves and a pre-3.57 body is byte-identical on the wire. (1) `POST /v1/rfq` on barrier OPENING requests accepts optional `levelOrigin` (`CLIENT` default \| `DERIVED`) and `clientClass` (`RETAIL` default \| `PROFESSIONAL` \| `ELIGIBLE`; only meaningful with a `clientUid`), unknown words `2013`, both frozen onto the contract at accept and serialized to the WRITER beside `capacity`/`clientTag` on every writer-visible surface of revision 3.57 (board rows, close-request frames, WRITER rows on `GET /v1/barriers`, writer `barriers` frames); never read by any pricing or risk rule. (2) Barrier quotes accept optional `premiumBps`, YOUR OWN premium share for THIS quote: at least the market's effective share for the request's variant (normal `10000 - knockOutRebateBps`, strict `strictTakeProfitPremiumBps`) and at most 10000, else `2044` (a writer can only make a contract MORE expensive than the market base, never cheaper); `2013` on a close request or a non-barrier market. The accept freezes the QUOTE's share onto the contract exactly where the market share is frozen, and every `premium`/`premiumBps` figure (margin admission, valuation, settlement, both parties' rows) reads the contract's frozen share. The requester's quote rows and the WS `quoted`/`myQuote` frames expose the quote's `premiumBps` so the holder prices the cost before accepting |
| 3.57 | 2026-08-26 | Writer transparency (section 6b, `/openapi.json` `info.version` 2.26.4 to 2.26.5). ADDITIVE, serialization only: no engine rule changes, no default moves. Board rows on `GET /v1/rfq/open` (and the portal board), the WRITER's copy of a close request's WS `rfq` frames, your WRITER rows on `GET /v1/barriers` (live and closed) and the WS `barriers` frames delivered to the writer gain `capacity` (`OWN_ACCOUNT` \| `FOR_CLIENT`) and, on `FOR_CLIENT` rows, `clientTag`: a 16-hex-char pseudonym of the end client behind the flow, stable per viewer (the same client of the same requester always tags the same toward you) and unlinkable across members; the raw `clientUid` never crosses members and is not derivable from the tag. Your own views as requester or holder are unchanged |
| 3.56 | 2026-08-24 | DOCUMENTATION revision, following member feedback: no wire change, no field added, removed or renamed, no default moved, and every request that worked at 3.55 works identically; `/openapi.json` `info.version` moves 2.26.3 to 2.26.4 because the document itself gains the descriptions below. Two facts the venue has served since revision 3.32 were reachable only through that changelog entry, which is why members kept asking for them. (1) **Lot rows carry YOUR OWN opening references** (section 6e): `openClOrdId`, the `clOrdId` you sent on the order whose fill opened the lot, and `openOrderId`, the venue order id it was accepted as. They are now in the field list, in the example response and in the OpenAPI `Lot` schema. They are transport independent, so a FIX order's `ClOrdID(11)` lands in `openClOrdId` exactly as a REST `clOrdId` does, which is what venue tag `9028` resolves against; both are OMITTED, not null, where the opening leg has no order of yours to name (no `clOrdId` sent, an RFQ acceptance leg, a venue-initiated open); and neither is a unique key, because one order filling in several prints opens one lot per fill and every one of them carries that order's reference. (2) **`GET /v1/lots/closures` is documented** (new subsection in 6e, and it now appears in `/openapi.json`): the close-attribution feed, every closing fill mapped to the lots it consumed by the same fold that builds `GET /v1/lots`, with `closeTradeId`, `lotId`, `positionSide`, `qty`, `openPrice`, `closePrice`, `realizedPnl`, `openClOrdId`, `closeClOrdId`, `flags`, `unsolicited` and `ts` per row, the `positionSide`, `from` and `to` filters, and the derived `KX-ADL-{closeTradeId}` and `KX-LQ-{orderId}` references on venue-initiated closes. Pinned by tests on both the served document and the wire. |
| 3.55 | 2026-08-20 | Barrier RFQ accept guard (section 6b; five new error codes, `/openapi.json` `info.version` 2.26.2 to 2.26.3). ADDITIVE: no field is added, removed or renamed, no default moves, and the rules are DORMANT until venue operations announce their activation on each environment (the sandbox will activate them together with a batch of earlier-announced admission rules; production runs them from its first day). On the accept of an OPENING barrier request only, five checks run against the CURRENT mark and the request's own terms: (1) the accepted quote's entry price must lie within 50 bps of the mark, and the mark itself must be FRESH (younger than the market's staleness timeout, 60 s on the current barrier listings), else the NEW code `2039` QUOTE_OFF_MARKET; (2) a `takeProfit` the mark has already reached (BUY: mark at or above it, SELL: at or below) rejects `2040` TAKE_PROFIT_REACHED, because the contract would terminate at the take profit on the first tick after opening; (3) a `barrier` the mark has already reached or crossed (BUY: mark at or below it, SELL: at or above) rejects `2043` BARRIER_REACHED, the loss-side mirror of `2040`, because the contract would knock out on the first tick after opening, forfeiting the holder's premium; (4) a `takeProfit` closer than 10 bps of the entry to the accepted entry rejects `2041` TAKE_PROFIT_TOO_CLOSE; (5) a `barrier` closer than 10 bps of the entry rejects `2042` BARRIER_TOO_CLOSE (distances exactly at the floor pass). Create and quote validation are unchanged; the EARLY-CLOSE accept is deliberately not banded against the mark (a fair close price is the contract's remaining value, not the spot; its fences remain the contract's terms, the close admission and the instrument price band). Nothing trades on any of these rejects; re-request or wait for fresh quotes. GUIDANCE FOR QUOTING MEMBERS: a quote is priced against the mark you saw, so refresh or withdraw your barrier quotes when the mark moves; a stale quote can otherwise be accepted against you right up to these fences. Companion updates: complete code reference, interactive console, documentation site. |
| 3.54 | 2026-08-19 | Member web portal two-factor sign-in (section 6g; portal only, no `/v1` change, `/openapi.json` `info.version` stays 2.26.2): after the member code and password the portal asks for a six-digit time-based code from an authenticator app (Google Authenticator, Microsoft Authenticator, Authy, 1Password or any TOTP app), enrolled at your first sign-in from a QR code and text key shown on the sign-in page; five wrong codes end the attempt, ten inside fifteen minutes lock the second step for fifteen minutes; a lost authenticator is reset by venue operations, which ends every signed-in portal session of your member at once. API keys, the `/v1` API, WebSocket and FIX are unaffected. Companion document: Member Portal User Guide revision 1.3, section 1.1. |
| 3.53 | 2026-08-18 | RTS 22 identity surfaces (section 6d; `/openapi.json` `info.version` 2.26.1 to 2.26.2). ADDITIVE: no field is removed or renamed and every 3.52 request works identically in the rollout stage now in force. (1) The RTS 22 Annex II per-country identifier priority table is applied to natural-person client identities: `nationalIdType` must be a label the country designates (the table and its aliases are printed in section 6d, `NATIONAL_ID` stays accepted as the generic alias of the first national number where the country's row lists one), `identifierPriority` defaults to the table rank and must agree with it when sent, checksummed identifiers (IT, ES, PT, PL, EE, LT) are verified, and CONCAT is accepted only where the Annex lists it. A lower-priority choice draws a warning; a contradiction is, in the WARN stage now in force, stored as submitted and reported in a new `warnings` array on the PUT echo (omitted when empty and on GET), and will reject with `2030` once venue operations announce the ENFORCE stage. (2) The echo and GET carry `mifirIdScheme` (`LEI`, `NIDN`, `CCPT`, `CONCAT`), the ISO 20022 scheme the identifier is reported under; empty on pre-3.53 records until the venue's re-validation fills it. (3) New route `PUT` / `GET /v1/trader-ids/{traderId}/identity` for the natural person or algorithm behind a Trader ID (RTS 22 fields 57 to 60): PERSON with the client identity rules plus a mandatory `branchCountry`, or ALGO with `algoId`; the venue's reports resolve `reg.traderId`, `reg.executionDecisionId` and `reg.investmentDecisionId` against it and carry unresolved codes as sent (correct for MiFID II firm members). Pinned by tests. |
| 3.52 | 2026-08-18 | Client identity chase list widened to the barrier markets (section 6d, `GET /v1/clients/identity/missing`; `/openapi.json` `info.version` 2.26.0 to 2.26.1, description text only). A barrier contract is a derivative traded on the MTF and its opening and closing trades are transaction-reportable under MiFIR Article 26(5) exactly like a perpetual trade, so the venue's reporting scope is the perpetual AND the barrier markets; the missing report now lists your `clientUid`s that traded on either kind with no identity record on file (before this revision barrier `clientUid`s were left out and no chase list existed for them; the identities were always owed). Spot markets stay outside (MiCA). No request or response field changes; members that stamp barrier requests and quotes with end-client `clientUid`s (the barriers broker suite) should expect new entries. Companion updates: REST integration manual, Integration Pack, documentation site REST page. |
| 3.51 | 2026-08-18 | STAKING SWAPS: a fourth market kind, `SWAP`, and six sandbox listings, `USD-TBILL-SWAP`, `EUR-TBILL-SWAP`, `BTC-STAKE-SWAP`, `ETH-STAKE-SWAP`, `SOL-STAKE-SWAP`, `USD1-STAKE-SWAP` (new section 6h; `/openapi.json` `info.version` 2.25.2 to 2.26.0). ADDITIVE on every surface: no field is removed or renamed, no default moves, and a client that never touches a SWAP market sees only new optional members and new enum values (`kind` `SWAP`, ledger kinds `SWAPPRINCIPAL` / `SWAPSETTLE`, trade flag `SWAP`), all documented as open sets since 2.1. (1) A swap market is RFQ-only and writer-first: `POST /v1/rfq` on it posts a standing OFFER (`qty`, `rateBps`, `tenorDays`, optional `minQty` and `expiresInMs` 60 s to 30 days, default 24 h; NO `side` / `barrier` / `closeContractId`, 2013; `rateBps` / `tenorDays` / `minQty` on any other market also 2013), `GET /v1/rfq/open?symbol=` is the anonymous OFFERS BOARD (rate descending, own offers excluded, Market Segments applied), `POST /v1/rfq/{rfqId}/accept` with `qty` and NO `quoteId` TAKES a size (partial takes allowed, `remainingQty` on every view; the answer carries `contractId`, `tradeId`, `notional`, `reward`, `maturityTs`, `asset`, `fee`), `POST /v1/rfq/{rfqId}/cancel` withdraws the offer; quotes do not exist on swap markets (2037). (2) Economics, exact: principal moves taker to writer at the take in the ASSET (`baseAsset`; USD, EUR, BTC, ETH, SOL or USD1), the reward is `floor(notional x rateBps x tenorDays / 3,650,000)` frozen at the take (ACT/365), maker/taker fees in the asset at the take only (1 / 2 bps on every listing), principal plus reward return at maturity (`openedTs + tenorDays x 86,400,000 ms`) through the venue timer; a short writer is settled partially and the rest stays as the taker's CLAIM (contract `DEFAULTED`, the writer's offers withdrawn, new offers refused with 3008 until every claim is cured from later balance, `CURED`); a delisting settles open contracts at principal plus the pro rata reward (`FINAL_SETTLEMENT`). The writer's obligation is UNSECURED and the venue never verifies what the writer does with the asset; the contract specification sheet says so in one sentence and members must disclose it to their clients. (3) New `GET /v1/swaps?status=OPEN|CLOSED|ALL` (both roles, counterparty never named, `paid` / `outstanding` / `status`), new private WS channel `swaps` (`opened`, `matured`, `default`, `cured`, `finalSettlement`, role-signed `amount`, guaranteed and cursor-backfillable), `rfq` frames for the offer lifecycle (`accepted` to both parties with `role` and the taken `qty`), executions flagged `SWAP` (`price` = `rateBps`, taker side `SELL`), ledger entries `SWAPPRINCIPAL` and `SWAPSETTLE` with `ccy` = the asset. `GET /v1/instruments` publishes `swapMinTenorDays`, `swapMaxTenorDays`, `swapMaxRateBps` and `baseAsset` on SWAP markets (`tickSize` 1, no bands, no funding, no tiers, no mark; `minNotional` is the minimum single take in the asset). (4) New codes 2036 SWAP_TERMS, 2037 SWAP_OFFER_ONLY, 2038 SWAP_SIZE, 3008 SWAP_WRITER_BLOCKED; 3004 and 3007 gain swap cases (taker asset funds; an unfit writer, offer withdrawn; a writer at its caps, offer stays); a zero or off-grid `qty` / `minQty` is `2002` on both routes; an offer of a writer outside your Market Segments answers `2024` before every other accept check. No early close in this release; swaps ride through a party's liquidation, and a writer in default management pays no settlement until it is out of it (the claim is cured then); a delisting withdraws the standing offers; offers and takes are not offered over FIX (the take's print reaches FIX drop copy and the 35=X tape as `9014=SWAP`); outside RTS 24 / RTS 22. Companion documents: new `KronosX-Contract-Specifications-Swaps.md`, code reference revision 2.24. |
| 3.50 | 2026-08-18 | Spot listings BTC-USD, ETH-USD, SOL-USD, BNB-USD, XRP-USD (kind SPOT, mode ORDER_BOOK, USD-quoted and USD-settled, one book per base asset) on the sandbox, and fractional spot sizing: a spot market now carries the same `qtyStep` as the perpetual of the same underlying (0.0001 BTC, 0.001 ETH and BNB, 0.01 SOL, 1 XRP), and a spot fill delivers exactly `qty` of the base asset against `price × qty` of USD; sections 2a (listing table, per-kind sizing) and 6a (spot listing table, base asset codes, `assets` on `GET /v1/account`, `wallet` asset legs, MiCA scope). Correction: earlier revisions said spot traded whole units (`qtyStep` `1`); that described the pre-listing fixture, never a rule, and no listed market changed. New companion document, Contract Specifications, USD-Settled Spot Markets (draft, downloads). `/openapi.json` `info.version` 2.25.1 to 2.25.2 (description text only, no field or schema change). No new fields, routes, channels or codes; the fixed 3001 / 3004 / 2016 / SPOT_UNFUNDED contract of section 6a is unchanged. Pinned by tests. |
| 3.49 | 2026-08-17 | CONCAT derivation aligned exactly to the ESMA transaction reporting guidelines (ESMA/2016/1452 section 5.5.1), section 6d; `/openapi.json` `info.version` 2.25.0 to 2.25.1 (description text only, no field change). The venue now applies all four ESMA steps itself: titles are removed from both name fields, the listed surname prefixes are removed when they stand as separate leading words (attached prefixes such as `McDonald` or `O'Brian` are kept), only the first first name is used, and the ESMA transliteration table is applied EXACTLY (its 150 characters fold to their listed letter, `Ș`, `Ț` and capital `ẞ` included; every other character, apostrophes, hyphens, spaces, digits, other scripts and Latin letters outside the table such as `Ā`, `Ė`, `İ`, `Ū`, is removed rather than approximated). Previously the venue folded a wider set of Latin look-alikes and asked members to submit bare names, which nothing enforced. Members submit names as held; every ESMA worked example derives verbatim. Stored CONCAT identifiers derived under the earlier fold are RE-DERIVED by the venue under the new rule: where the value differs the record is corrected under a new audit `revision` (the earlier revisions keep what was on file, so a corrected transaction report can show both), visible on `GET /v1/clients/{clientUid}/identity`; LEI and national identifiers are never touched. The `2030` refusal for a name that folds to no Latin letter now reads `name does not transliterate to Latin letters: resubmit firstName and surname in transliterated Latin form (ESMA transaction reporting guidelines, section 5.5.1) or supply nationalId and nationalIdType`. Pinned by tests. |
| 3.48 | 2026-08-17 | Audit follow-up (`/openapi.json` `info.version` 2.24.0 to 2.25.0). (1) LOCAL_DAILY funding (section 7): `fundingRate`, `fundingRatePct`, `fundingRateAnnualizedPct` and `fundingPerUnit` are now OMITTED on a LOCAL_DAILY market on `GET /v1/funding/current`, `GET /v1/ticker` (REST and the WS `ticker` channel) and the WS `funding.{symbol}` rate frame; previously they echoed the reference feed's stamped field (`0`, or the sandbox simulator's grid constant), which was never the rate the venue charges. GRID markets are unchanged; the settlement frame and `GET /v1/funding/history` keep publishing the settled rate. The sandbox simulator no longer stamps the grid constant on LOCAL_DAILY markets. (2) Barrier early close (section 6b): the writer now also receives the close request's terminal WS `rfq` `canceled` / `expired` frame (`role` `WRITER`, `closeContractId`, no requester reference), including the cancel that follows the contract's own termination; a close request that omits `clientUid` is admitted by the trading-capacity gate for MTCH / AOTC members (it was wrongly classified as house flow and rejected `2033` at ENFORCE); the `2010` message now says `clientUid required, or on a barrier close request not the contract's holder-side client`. (3) Restarts (section 10.7): an op the venue had already read when the restart began completes and its ack is written before the 1001 close; anything read after the trip is not processed and gets no ack (re-send after reconnecting), so no command ever executes without its ack. FIX: a cluster leader change logs sessions out with `Text(58)="leader change, reconnect"` rather than `venue restart` (FIX guide section 1). Pinned by tests. |
| 3.47 | 2026-08-17 | Audit follow-up, three behaviour clarifications and no new field. (1) Barrier early close (section 6b): the holder's accept now also requires that the close leaves both parties solvent. Closing releases the contract's margin, so a close whose settlement plus fee costs a party no more than it releases is always admitted; otherwise the party's available balance must stay non-negative afterwards, else `3001` (holder) or `3007` (writer, e.g. a quote far off the mark) with nothing moved. Close requests pass every other RFQ gate unchanged with the ordinary codes per step (create: halted `3003`, delisted `3006`, no mark `3005`, reduce-only or stale market or member `3002`; quote and accept: halted, delisted, reduce-only or stale market `3003`, member status `3002`; size cap `2013` and minimum notional `2028` on create and quote), now stated explicitly. (2) LOCAL_DAILY funding (section 7): only marks sequenced inside the VWAP window feed the settlement mean, and on the schedules whose snapshot time is the window end the position snapshot is taken at most 60 s after the end; a longer outage skips the day. Both are the documented rule of revision 3.42, now enforced. (3) No OpenAPI change (`info.version` stays 2.24.0). Effective from the venue's next replay-contract epoch, announced by venue operations. |
| 3.46 | 2026-08-17 | Documentation only, no wire change. (1) The informative "Current listings" table in section 2a now lists all 26 sandbox markets (the eight tradfi perpetuals of revision 3.42 and the twelve `*-USD-KO` barrier markets beside the original six) with tick, `qtyStep`, `minNotional` and the cap columns, and section 1a says that every `*-USD-KO` market, not only `BTC-USD-KO`, is sandbox-only for now. (2) Companion documents brought up to revisions 3.44 and 3.45: the complete code reference (revision 2.22: close code `1001` `venue restart` beside 1000 / 4008 / 4009, the close-request cases on `2013` and `2024`, `BARRIER_CLOSE` / `REVERSAL` in the flags enumeration), the Technical Handbook (revision 2.14: premium economics, strict take profit, `EARLY_CLOSE`), the Claude Code REST and WebSocket manuals (`strictTakeProfit` / `premium` / `premiumBps` on every barrier view and frame, `earlyClose` in the channel table, the worked example at the 1000 bps rebate), the Integration Pack (revision 3.6) and the Member Portal Guide (revision 1.2: the Strict TP toggle and the premium display), plus the documentation site reference page. |
| 3.45 | 2026-08-17 | Barrier economics: the KNOCK-OUT REBATE (cheaper contracts) and the STRICT TAKE PROFIT (a cheaper short-dated variant that pays only on a touch), plus a graceful WebSocket / FIX shutdown (section 6b, section 10.3, section 10.7; `/openapi.json` `info.version` 2.23.0 to 2.24.0). ADDITIVE on every surface: no field is removed or renamed, no default moves, and a client that never sends `barrier.strictTakeProfit` sees only new fields, so every 3.44 request works identically. (1) Instruments: `GET /v1/instruments` rows of BARRIER markets carry `knockOutRebateBps` (int, bps of the barrier distance; 1000 = 10 percent on every barrier market listed today) and `strictTakeProfitPremiumBps` (int; 5000 = 50 percent today; 0 = strict take profit not offered); omitted on PERP and SPOT. (2) The holder's maximum loss is now its PREMIUM, `|entry - barrier| x qty x contract value x premiumBps / 10000` (floor), with `premiumBps` FROZEN on the contract at the open: `10000 - knockOutRebateBps` on a normal contract, `strictTakeProfitPremiumBps` on a strict one, 10000 (the full distance) on every contract opened before this revision. Holder margin at entry = the premium; the live requirement = the remaining mark-to-barrier distance less the rebate (the premium plus the unrealized P&L, floored at 0); the holder's valuation is floored at minus the premium. Settlement: knock-out = minus the premium for the holder (the writer receives it, the rebate share of the distance is what the holder recovers); take-profit touch = the full `|takeProfit - entry| x qty x contract value`; expiry of a normal contract, tear-up, final settlement and early close = the price P&L floored at minus the premium and capped at the potential profit when a take profit exists; expiry of a STRICT contract without a touch = minus the premium whatever the mark. Writer margin unchanged (the rebate adds no writer requirement: its worst case stays the take-profit side). Fees unchanged. (3) `POST /v1/rfq` `barrier` gains `strictTakeProfit` (bool, default false): requires `takeProfit`, an `expiryTs` at most 24 h after the request and a market whose `strictTakeProfitPremiumBps` is nonzero, else `2027` (extended wording in section 9, no new code; re-checked at accept). (4) Every barrier RFQ view (the create response, `GET /v1/rfq/mine`, `GET /v1/rfq/open`, the WS `rfq` `created` frames incl. the writer's copy of a close request), the accept response (`POST /v1/rfq/{rfqId}/accept`, both an open and an early close), `GET /v1/barriers` rows (live and closed) and every WS `barriers` frame carry `strictTakeProfit` (bool), `premium` (money string: the holder's maximum loss; on an open RFQ view INDICATIVE at the current mark since the entry is the quoted price, on a close request the closed contract's frozen figure, elsewhere the frozen figure) and `premiumBps` (int, effective share, 10000 = full distance); omitted on perp / spot RFQ views. `realizedPnl` on an early close is clamped to [minus the premium, plus the potential profit]. (5) Graceful shutdown of the streaming transports (member feedback): when the venue stops for a rollout or a restart, every WebSocket (public and private, whatever it is subscribed to) first receives one advisory control frame `{"channel":"advisory","type":"advisory","advisory":"restart","msg":"The venue is restarting; reconnect and resubscribe.","ts":...}` and is then closed with status `1001` and reason `venue restart` inside a bounded window of about two seconds (guaranteed frames already released to the socket are written first), new WebSocket upgrades answer HTTP 503 with `Retry-After` while the venue is stopping, and every FIX session receives `Logout(35=5)` with `Text(58)="venue restart"` before the TCP close; subscriptions, `auth` and cancel-on-disconnect arming are not preserved across a restart, so reconnect with backoff, re-authenticate, resubscribe and recover per section 10.8 (section 10.7 "Restarts and reconnection", FIX guide 2.17 section 1). Pinned by tests: the instrument fields, the strict `2027` cases (no take profit, no expiry, expiry beyond 24 h, market not offering), the create echo and premium on every view, the accept answer, `GET /v1/barriers` rows and WS frames with `strictTakeProfit` / `premium` / `premiumBps`, a knock-out on a rebate market settling at minus the premium with zero-sum balances, a strict expiry without a touch, the served OpenAPI example `barrierStrictTakeProfit`, and the WS restart advisory then close 1001 on a private and a public socket, 503 on a late upgrade and the FIX restart Logout. |
| 3.44 | 2026-08-17 | Barrier contracts gain an EARLY CLOSE BY RFQ: the holder asks the writer for a price and the contract ends there (section 6b; `/openapi.json` `info.version` 2.22.0 to 2.23.0). ADDITIVE on every surface: no field is removed or renamed, no default moves, and a client that never sends `closeContractId` sees only new nullable members, omitted where they do not apply, so every 3.43 payload stays byte-identical. (1) `POST /v1/rfq` accepts `closeContractId` (int64): the id of an OPEN barrier contract you HOLD on `symbol`, making the request a CLOSE REQUEST to that contract's writer. `side`, `qty` and `barrier` MUST then be omitted (`2013`): the venue derives the closing side (a long holder `SELL`s, a short holder `BUY`s), the full contract quantity and the contract's terms, and echoes them on every RFQ view; `clientUid` may be omitted (it defaults to the contract's holder-side client) or must equal it (`2010`); `expiresInMs`, `clOrdId` and `reg` behave as on any request. NEW code `2035` (400): `closeContractId` is not an open contract you hold on this market, or a close request for it is already open (one at a time; also the accept-time answer when the contract terminated between the steps). (2) Only the contract's WRITER may quote a close request (any other member is answered `2024`), and it appears on the writer's board only: `GET /v1/rfq/open` and the WS `rfq` `created` frame, which the writer receives with `role` `WRITER` and no requester reference (the requester's copy says `role` `REQUESTER`). The writer's price is tick-aligned (`2002`), inside the band (`2003`) and STRICTLY INSIDE the contract's terms relative to the holder's direction (`barrier < price < takeProfit` for a long holder, mirrored for a short one), else `2027`. (3) The holder's accept (`POST /v1/rfq/{rfqId}/accept`) closes the contract AT the quoted price: a closing trade flagged `BARRIER_CLOSE` (a NEW value in the `flags` vocabulary, ranking above `RFQ`, visible wherever flags appear: public trades, `GET /v1/executions`, WS `execution` and `trades` frames, the FIX trade tape (35=X, venue tag 9014)) that pays maker/taker fees exactly like the entry trade (holder taker, writer maker, on `qty` x price x contract value); a `BARRIERSETTLE` zero-sum settlement (holder P&L = direction x (price - entry) x `qty` x contract value, writer the negation) with `terminationReason` `EARLY_CLOSE`; and the accept response gains `closeContractId` (echo), `settlePrice` and holder-signed `realizedPnl`, with `contractId` = the CLOSED contract. Re-checks at accept: contract still open and held (`2035`), quote still inside the terms (`2027`), band against the current mark (`2003`), statuses as today; NO margin admission for a close (closing releases margin), the exact-money fence stays (`2027`). (4) Every RFQ view gains `closeContractId` (int64, omitted on an ordinary request): the create response, `GET /v1/rfq/mine`, `GET /v1/rfq/open`, `GET /v1/rfq/quotes/mine` and the quote response, and EVERY WS `rfq` frame of the request (`created`, `quoted`, `myQuote`, `quoteCanceled`, `accepted`, `canceled`, `expired`); `created`, `canceled` and `expired` frames of a close request additionally carry `role` (`REQUESTER` on the requester's copy), and `created` frames carry `takeProfit` when the request has one. (5) Termination vocabulary: `terminationReason` `EARLY_CLOSE` on `GET /v1/barriers?status=CLOSED` rows (`settlePrice` = the accepted quote) and on the WS `barriers` channel, whose close frame `type` is `earlyClose`. (6) Coupling: while a close request is open the contract refuses a second one (`2035`); if the contract terminates first (knock-out, take profit, expiry, tear-up, final settlement) the venue CANCELS the open close request in the same command (`rfq` `canceled` frame, an ordinary cancel that still names the contract in `closeContractId`); cancelling a close request is free like every cancel. Pinned by tests: the create echo and 2013 / 2035 rejects, the writer-only board and the 2024 to any other quoter, the strictly-inside-terms 2027, the accept answer, fees and zero-sum balances, the `CLOSED` row with `EARLY_CLOSE`, the WS `rfq` `created` delivery to the writer only and the `barriers` `earlyClose` frame to both parties, and the served OpenAPI example. |
| 3.43 | 2026-08-16 | Barrier contracts gain an OPTIONAL TAKE PROFIT as a second exit level (section 6b; `/openapi.json` `info.version` 2.21.0 to 2.22.0). ADDITIVE on every surface: no field is removed or renamed, every request that worked at 3.42 works identically, and a knock-out-only request produces byte-identical responses (the new members are omitted, not null). (1) `POST /v1/rfq` on a barrier market accepts `barrier.takeProfit`, a price on the PROFIT side of the entry (above it for `BUY`, below it for `SELL`, so the barrier and the take profit always sit on opposite sides of the entry), validated against the current mark at create and against the QUOTED entry at quote and accept, strictly beyond it by at least one tick, else `2027` (off the tick grid: `2002`); a `takeProfit` on a non-barrier market is a `barrier` object on a non-barrier market and rejects `2027` as before. When the mark reaches it (long: at or above; short: at or below) the contract terminates AT the take-profit price, fee-free like a knock-out, `BARRIERSETTLE` zero-sum, holder profit `|takeProfit - entry| x qty`; a gap through the level still settles AT it. Holder margin is unchanged; the writer's requirement becomes the smaller of the tiered figure and the remaining mark-to-take-profit distance times `qty` times the contract value. Expiry with a take profit set behaves as before (mark settlement). (2) The create response, `GET /v1/rfq/mine` and the open board `GET /v1/rfq/open` echo `takeProfit` when set; the accept response echoes the opened contract's terms (`barrier`, `takeProfit`, `contractExpiryTs`) beside `contractId`. (3) `GET /v1/barriers` rows carry `takeProfit` (when set) and `status` (`OPEN`), and the route gains `?status=OPEN|CLOSED|ALL` (default `OPEN`, unchanged) plus `limit`: `CLOSED` returns your recently settled contracts (newest first, kept since the last venue start) with `settlePrice`, role-signed `realizedPnl`, `terminationReason` (`KNOCK_OUT` \| `TAKE_PROFIT` \| `EXPIRY` \| `TEAR_UP` \| `FINAL_SETTLEMENT`) and `closedTs` in place of the mark-derived figures; any other `status` value answers `2013`. The writer's `margin` on a live row reflects the take-profit bound. (4) WS `barriers` channel (section 10.3): new close `type` `takeProfit`; every frame carries `takeProfit` when set, and every close carries `terminationReason` in the REST vocabulary; the existing types and members are unchanged. Pinned by tests: create echo on every view, 2027 on the wrong side of the mark and of the quoted entry, 2002 off-grid, 2027 on a non-barrier market, the accept echo, live-row `takeProfit`/`status`/writer margin bound, termination AT the level with zero-sum balances, the `CLOSED` row and the WS frames to both parties. |
| 3.42 | 2026-08-16 | New PERP markets on the sandbox settled on a DAILY index-VWAP funding methodology, and the funding SCHEDULE surface that describes it (`/openapi.json` `info.version` 2.20.0 to 2.21.0). ADDITIVE on every surface: no field is removed or renamed, no default moves, and every request that worked at 3.41 works identically. New listings: `SP500-USD-PERP`, `NDX100-USD-PERP`, `XAU-USD-PERP`, `WTI-USD-PERP`, `BRENT-USD-PERP`, `EUR-USD-PERP`, `GBP-USD-PERP`, `JPY-USD-PERP` (each 1 base unit = 1 index point, troy ounce, barrel or unit of the base currency priced in USD; `JPY-USD-PERP` quotes USD per JPY), plus barrier RFQ markets `*-USD-KO` for every underlying that lacked one; read `GET /v1/instruments` for their sizing. (1) `GET /v1/instruments` and `GET /v1/funding/current` (and the WS `get.instruments` / `get.funding` reads) carry a `funding` block: `schedule` GRID (the historical fixed UTC anchor grid, `intervalHours`) or LOCAL_DAILY (one settlement per calendar day at a local wall-clock window: `zone` UTC \| LONDON \| NEW_YORK, `snapshotLocalTime`, `vwapWindow`, `vwapSnapshots`), `nextSettlementTs` (the same instant as `nextFundingTs`) and, on LOCAL_DAILY, `nextSnapshotTs`; omitted on spot and barrier markets. Section 7 documents the methodology: at the snapshot time the venue captures every open position and the index, over the window it records one mark per minute, and at the window end it settles rate = clamp((mean of the recorded marks - index at the snapshot) / index at the snapshot, floor, cap), positive meaning longs pay, applied to the SNAPSHOT positions with the mean as the settle mark; a day with no snapshot (venue outage) is skipped, never charged later. On these markets `fundingIntervalHours` reads 24 and `fundingRateAnnualizedPct` annualises over one day. (2) `predictedFundingRate`, `predictedFundingRatePct` and `predictedFundingPerUnit` on `GET /v1/funding/current`, `GET /v1/ticker` and the WS `funding.{symbol}` rate frame are, on a LOCAL_DAILY market, the rate the marks recorded so far would settle at, and are OMITTED (not null, never a fabricated zero) before the day's window opens; unchanged on GRID markets. (3) `GET /v1/funding/history` rows gain `settlementValue`, `indexAtSnapshot`, `snapshotsUsed` and `snapshotTs` on LOCAL_DAILY settlements (omitted on GRID rows), and the WS `funding.{symbol}` channel gains `type: "settlementDetail"` carrying the same figures after each `settlement` frame; the same frame is stored in the member event stream as eventType `FUNDING_SETTLEMENT_DETAIL` (section 11), and the stored `FUNDING_SETTLED` row now carries `fundingPerUnit` exactly like the live frame. (4) Section 1a: the sandbox's fixed +0.0001 rate applies to GRID markets only; LOCAL_DAILY markets compute their rate on the sandbox exactly as in production. Sandbox marks for the new markets are reference prices from an external dex (mark and index separately), a sandbox device like the crypto reference feed. Pinned by tests: the funding block per schedule, predicted omission before the window and the running estimate inside it, the LOCAL_DAILY listing keys, and the timer firing. |
| 3.41 | 2026-08-14 | Correction to 3.40, no wire change: the book `checksum` is NOT always over the top 25 whatever depth. A SNAPSHOT (REST `GET /v1/orderbook` and the WebSocket snapshot) checksums the levels it carried, capped at the top 25 per side, so `min(depth, 25)`: at depth 1, 5 or 25 it covers exactly the rows you received, and only at depth 100 does it cover the top 25 of the 100. A DELTA checksum is always over the top 25 of the full book, so continuous delta verification needs a subscription depth of 25 or 100; a depth 1 or 5 subscriber cannot recompute the delta checksum and should verify snapshots only. Section 5, section 10.4 and the endpoint table are corrected, and the section 10.4 worked snapshot example (which had shown one bid and one ask against the full-book checksum) now shows the full two-level book so its published checksum matches; pinned by test at depth 5 and depth 100 and by a worked-example checksum test. |
| 3.40 | 2026-08-14 | Documentation clarification, no wire change, following member feedback: the book `checksum` is always computed over the top 25 levels only, on EVERY frame, REST snapshots and WebSocket snapshots and deltas alike, whatever `depth` is requested or subscribed. A `depth=100` snapshot returns 100 rows but its checksum still covers only the top 25; verify it against your own top 25 levels, never against all 100. Revision 3.24 stated this and pinned it by test, but one pre-3.24 sentence survived on the documentation site's WebSocket page ("a snapshot's checksum covers only the levels the snapshot contained") and the REST page said "over the returned rows"; both are corrected, and section 5 (`GET /v1/orderbook`) and section 10.4 of this document now state the rule explicitly as well. |
| 3.39 | 2026-08-14 | Clarification, no wire-behavior change: this venue is operated as a pure MTF and runs NO insurance fund (disabled on every profile). `GET /v1/insurance-fund` has always returned `enabled: false` with zero balances; the endpoint table, the margin-model liquidation-waterfall summary, the `LIQUIDATION_COMPLETE` example and the served OpenAPI example are corrected to match. A defaulter's residual after auto-deleveraging books to the venue, not to a mutualized fund, so `insuranceFundDelta` on an episode is always 0. `/openapi.json` `info.version` stays 2.20.0. |
| 3.38 | 2026-08-14 | New public market-data endpoint `GET /v1/klines` (`/openapi.json` `info.version` 2.19.0 to 2.20.0), documented in section 5: OHLCV candles for charting. Query `symbol` (required), `interval` one of 1m/5m/15m/30m/1h/4h/1d (default 1h; any other value answers 400), optional `from` (Unix ms, bounded to the most recent `limit` intervals) and `limit` 1 to 1000 (default 200). Rows return oldest first as `{t, open, high, low, close, volume, trades}`, where `t` is the bucket open time in Unix ms. REST only; no WebSocket op. |
| 3.37 | 2026-08-12 | Member web portal additions, documented in new section 6g (portal only; no `/v1` change, `/openapi.json` `info.version` stays 2.19.0): the Inquiries tab (venue-opened message threads your operations staff can answer while open) and the Onboarding tab (self-onboarding form and document upload, visible only until venue operations mark the firm onboarded; portal logins and API keys never depend on onboarding state). A Member Portal user guide ships as a separate document. |
| 3.36 | 2026-08-12 | Targeted lot close by the member's OWN reference on FIX (FIX Gateway guide 2.15); no `/v1` change, `/openapi.json` `info.version` stays 2.19.0. New venue tag `9028` (TargetOpenClOrdID) on NewOrderSingle carries the `clOrdId` of your OPENING order; the venue resolves it to the lot or lots that order opened (one lot per opening fill, oldest first, up to the close quantity) and records the same assignments as tag `9020`. When a reference was reused after the 24 hour uniqueness window the most recent order bearing it wins. `9020` and `9028` together, an over-length value, or either tag on a venue without the lot ledger answer 2013. Section 6e transport note updated. |
| 3.35 | 2026-08-12 | Targeted lot close reaches FIX (FIX Gateway guide 2.14); no `/v1` change, `/openapi.json` `info.version` stays 2.19.0. New venue tag `9020` (TargetLotID) on NewOrderSingle names the open lot the order should close, feeding the same assignment record as `POST /v1/lots/{lotId}/close`; the member supplies side, quantity and hedge bucket itself, an unknown or already-closed target degrades to FIFO, and the closures feed shows the lot actually consumed. Section 6e transport note updated. |
| 3.34 | 2026-08-12 | Corrections after member feedback (`/openapi.json` `info.version` 2.18.0 to 2.19.0). `GET /v1/lots/closures` now includes ADL tear-ups, sourced from the per-client ADL record (the ADL trade itself carries no client identifiers by design) and attributed to the correct hedge bucket; an ADL row's `closeClOrdId` derives as `KX-ADL-{tradeId}`; a defaulted member reconciles its own side via `GET /v1/unwinds`. Statement correction in 3.32 and the Netting and Unsolicited Flow Statement (now SIX venue-initiated mechanisms incl. trade reversal): liquidation and ADL executions always reduce, but a REVERSAL undoes the original trade and can carry `positionEffect` `OPEN` or `INCREASE` when the reversed trade had closed a position. Always read the stamped `positionEffect`. |
| 3.33 | 2026-08-12 | Transport completion for the 3.32 integration additions (`/openapi.json` `info.version` 2.17.0 to 2.18.0). FIX drop copy execution reports gain venue tag `9019` carrying `positionEffect` on fill legs (FIX Gateway guide 2.13). `GET /v1/ticker` gains `fundingPerUnit` and `predictedFundingPerUnit`; `GET /v1/funding/history` rows gain `fundingPerUnit` computed from each settlement's own rate and mark. |
| 3.32 | 2026-08-12 | Member integration additions (`/openapi.json` `info.version` 2.16.0 to 2.17.0). Lot rows on `GET /v1/lots` gain `openClOrdId` and `openOrderId`: your OWN references on the opening fill, so lots are addressable by your client-generated ids without a mapping table. New `GET /v1/lots/closures`: the close-attribution feed, every closing fill mapped to the exact lot(s) it consumed by the same FIFO fold that builds `/v1/lots`, with `flags` and an `unsolicited` marker so liquidation, ADL and reversal closes reconcile to specific lots. Execution rows (`GET /v1/executions`, portal trades, WS `execution` frame) gain `positionEffect`: `OPEN`, `INCREASE`, `PARTIAL_CLOSE`, `CLOSE` or `FLIP`, stating what the fill did to your client sub-position, so open versus close never needs inference. Liquidation and ADL executions always reduce; a REVERSAL undoes the original trade, so it can carry `OPEN` or `INCREASE` when the reversed trade had closed a position: always read the stamped value. Every funding surface gains `fundingPerUnit` and `predictedFundingPerUnit`: the charge for ONE unit of quantity at the current mark, settlement currency per interval, so you compute each client's charge locally by multiplying by position size. |
| 3.31 | 2026-08-11 | Portal statements: the monthly IFR data annex moves to v2 and gains a DETACHED Ed25519 signature (signature header, `.sig` sibling download, published verification key); the annex appends the month-end K-TCD exposure snapshot. Portal only; no `/v1` change. |
| 3.30 | 2026-08-11 | Trade flags gain `REVERSAL` (`/openapi.json` `info.version` 2.15.1 to 2.16.0; no request surface changes). The venue may exercise its rulebook cancellation and reversal power for an erroneous trade by booking an equal-and-opposite compensating trade at the ORIGINAL price between the SAME two members: the original trade record is never edited or deleted, the compensating trade carries flag `REVERSAL` and the venue-generated reference `KX-RV-{originalTradeId}` on both legs, it is fee-free, it does not print to the public last-trade price, it never triggers conditional orders, and it is excluded from order-to-trade ratios. Affected members see the compensating fill and any realized PnL ledger entry exactly like any fill, on drop copy (the `KX-RV-{originalTradeId}` reference in `ClOrdID(11)` and `positionEffect` in venue tag 9019) and on the FIX trade tape (35=X, venue tag 9014 `REVERSAL`), execution history and WS trade frames. Separately, members auto-deleveraged in a counterparty default may receive a `RECOVERYDISTRIBUTION` ledger entry: a pro-rata share of any amount the venue later recovers from the defaulted member (rulebook para 71), credited to the account with the episode reference. |
| 3.29 | 2026-08-10 | Documentation only, the trading capacity field spec rewrite; no wire change, no behaviour change, `/openapi.json` `info.version` moves 2.15.0 to 2.15.1. Section 6f now states the VENUE semantics of `reg.tradingCapacity` exactly: the field is spelled in the MiFIR vocabulary but is NOT MiFIR field 29; it declares the legal name of execution (`DEAL` own account; `MTCH` own name for a client account INCLUDING commission business, Finanzkommissionsgeschaeft, regardless of the home-regime report label; `AOTC` agency in the name of the identified client), and the RTS 22 report label derives from the configured per-member mapping, never the other way around. A commission broker whose home practice reports AOTC tags `MTCH` on the wire; the report mapping is configured at onboarding together with the licensing profile and a capacity-tagging attestation. The venue's RTS 22 extracts now carry per-leg field 29 report-label columns computed from that mapping at the extract edge, and the IFR data annex prints the capacity semantics as a methodology note. Companion updates: Member Technical Handbook (2.8 to 2.9, new section 10a with the same wording), interactive console (`/openapi.json` 2.15.1), documentation site. |
| 3.28 | 2026-08-10 | RegData Phase 2 step 1 (ADR-15): one new error code, production posture stated, no request shape changes, `/openapi.json` `info.version` moves 2.14.0 to 2.15.0. (1) NEW code `2034` (HTTP 403): in PRODUCTION the licensing registry runs fail-closed, refusing order acceptance (order entry, RFQ create/quote/accept, lot close) for a member with no configured licensing profile; profile configuration is a mandatory onboarding artifact. The SANDBOX stays fail-open and never answers 2034, so nothing changes for sandbox integration. (2) Per-member RTS 22 field 29 report labels: where a member's home regime reports matched principal as AOTC, the venue configures the REPORT label per member; the wire vocabulary, validation and prudential bucketing are unchanged and key on the capacity itself (own-name execution always accrues to dealing on own account, IFR Article 4(1)(33)); portal and admin surfaces now show the internal capacity name as primary wording with the wire code secondary. (3) Venue-side: Art. 26 risk-factor buckets, Art. 29 supervisory factors with the crypto and Barrier class mapping proposal, Art. 30 collateral haircuts and the SNI WARN share seeded into the parameter store with citations, all at TODO(compliance-signoff), preparing the Phase 2 exposure engines. Companion updates: complete code reference (2.18 to 2.19), interactive console (`/openapi.json` 2.15.0), documentation site. |
| 3.27 | 2026-08-10 | RFQ flow now classifies by trading capacity, closing the limit revision 3.26 disclosed; no request shape changes anywhere and `/openapi.json` `info.version` moves 2.13.0 to 2.14.0. The `reg` object an RFQ quote carries (`POST /v1/rfq/{rfqId}/quotes`, and the portal quote form) is now journaled on the venue's regulatory record exactly as the create's always was, and an acceptance stamps BOTH legs' records onto the trade: the maker leg of an RFQ or barrier trade buckets by the QUOTE's `reg.tradingCapacity`, the requester leg by the CREATE's, in the DTF/COH flow statements and the IFR data annex. Carry `reg.tradingCapacity` on every quote; a quote without one classifies its maker leg as UNKNOWN. History is never reclassified: RFQ trades executed before this revision keep counting as UNKNOWN capacity, because the quote side was never recorded for them, and the coverage percentage carries that honestly. Under ENFORCE-stage capacity enforcement nothing changes: quotes already required a valid capacity since 3.25; what is new is that the venue now KEEPS it. Venue-internal note with no wire impact: the venue's snapshot format version moves with this release, so the first restart recovers by one full journal replay. Companion updates: interactive console (`/openapi.json` 2.14.0), documentation site. |
| 3.26 | 2026-08-10 | Same-day corrections to revision 3.25, following the venue's adversarial audit of the capacity release; one additive field, no removals, no renames, `/openapi.json` `info.version` moves 2.12.0 to 2.13.0. (1) `POST /v1/lots/{lotId}/close` accepts an optional `reg` object, same shape and rules as order entry, because a lot close IS order entry and a member at ENFORCE-stage capacity enforcement needs a carrier there or every lot close would reject 2033; ungated members see no change. (2) FIX `OrderCapacity(528)` is read ONLY for gated members (stage WARN or ENFORCE): at OFF the tag stays entirely unparsed exactly as before revision 3.25, preserving the zero-change-at-OFF promise on FIX; at WARN an unmappable or conflicting value is counted venue-side and ignored; at ENFORCE it refuses with 2033 as documented (FIX rules of engagement 2.11 to 2.12). (3) Section 6f now states the current classification limits plainly: RFQ and barrier executed value reports as UNKNOWN capacity until a later release journals the quote side's capacity; the coverage percentage floors and shows empty on no-fill scopes; DEAL enforcement needs a configured house reference for non-market-maker members; unresolvable parameters produce PARAMETER_ERROR on the statement, never a fabricated figure. (4) Venue-side hardening with no wire shape change: statement generation pins provenance to one engine incarnation, an empty-month annex is a fixed deterministic form, portal rolling figures are documented as a live view against the current parameter version (the annex remains the pinned artifact), the nightly aggregation skips loudly when the projection store is behind rather than record an undercounted day, historical margin captures are never overwritten by re-runs, and the licensing profile store participates in cluster refresh. Companion updates: FIX rules of engagement (2.12), interactive console (`/openapi.json` 2.13.0), documentation site. |
| 3.25 | 2026-08-10 | Trading capacity enforcement (staged, per member) and the RegData regulatory surface (new section 6f); one new error code; `/openapi.json` `info.version` moves 2.11.2 to 2.12.0. NO field is added, removed or renamed anywhere, no default moves, and a member whose licensing profile has not been configured, or is configured OFF, sees ZERO change on any request: the existing `reg.tradingCapacity` field keeps being recorded verbatim exactly as before. What is new: (1) the venue operator can now record per member which MiFIR capacities (`DEAL`, `MTCH`, `AOTC`) the member's licence permits and stage enforcement per member, OFF (default, today's behaviour), WARN (violations counted venue-side and reviewed with the member, orders NOT rejected), ENFORCE (a violating order, RFQ creation or RFQ quote rejects with the NEW code `2033`, HTTP 400, before the matching engine, like 1005/1003/2021; the rules are in section 6f). Margin previews are never capacity gated. (2) FIX: standard `OrderCapacity(528)` is now also accepted on NewOrderSingle, mapping `P` and `G` to `DEAL`, `R` to `MTCH`, `A` to `AOTC`; venue tag 9021 stays the canonical carrier, an unmappable 528 refuses with 2033, and when both tags are sent they must agree (FIX rules of engagement moves 2.10 to 2.11). (3) The member portal gains the Regulatory tab: daily DTF/COH flow statements (executed value per UTC day split by capacity, cash vs derivatives), the rolling K-DTF and K-COH input measures with the Article 15(2) coefficients applied, capacity coverage, and downloads including the monthly IFR data annex v1 (unsigned; the signed variant follows), every figure carrying the fixed disclaimer: venue-attributable data only, aggregated by the member across venues, not regulatory or legal advice. Statements record the parameter version, engine incarnation and projection watermark they were generated from and regenerate byte-identical. Values recorded before enforcement are never reclassified; nonconforming history counts as UNKNOWN capacity and shows in the coverage percentage. Companion updates: complete code reference (2.17 to 2.18), FIX rules of engagement (2.10 to 2.11), interactive console (`/openapi.json` 2.12.0), documentation site. |
| 3.24 | 2026-08-07 | Documentation only, following integration feedback; no wire change, no behaviour change, and every request that worked at 3.23 works identically. `/openapi.json` `info.version` moves 2.11.1 to 2.11.2 as a documentation revision. Eight clarifications, each stating something the venue already does: (1) the orderbook `checksum` on EVERY frame, snapshot and delta alike, covers at most the top 25 levels a side, so a depth-100 snapshot is verified against its first 25 rows a side, never all 100 (the WS quick guide previously said a snapshot's checksum covers the rows it carries, which is wrong at depth 100; a depth-100 pinning test now guards the distinction); (2) the WS `margin` channel note: lowercase `mm` is the same number `GET /v1/account` reports as `MM`, `im` equals `positionIM` plus `orderMargin`, spellings frozen; (3) `clientUid` economics stated where the field is introduced, not only on the 2029 row: the venue-wide identifier table caps at 1,048,576, entries are never pruned, so mint one stable identifier per real end client, never one per session; (4) dust: a client sub-position worth less than `minNotional` cannot be closed by a plain client-level order (2028; the floor exempts only genuinely flagged reduce-only orders, which client-level closes are not), the client-attributed escape is enlarge then close whole with the enlarging order aligned UP to the qty grid, and a member-level `reduceOnly` order closes dust directly where the member book covers it; (5) the 2003 row and the do-not lists now say to derive resting-order offsets from `priceBandBps` and a live mark at runtime, never a hard-coded price; (6) the 2012 row publishes the default member-wide open-order cap of 10,000 and points at the portal limits card for your live value; (7) section 1a states the sandbox funding rate is fixed at +0.0001 per 8 hour interval and never varies on its own (production computes a real rate; operator-injected rates available for testing via the members Slack channel), and section 1 no longer implies the sandbox simulates funding variation; (8) conformance plan T-04 now states its precondition: OTR thresholds are per member and unset by default, so venue operations must configure one before the test is executable. Companion updates: complete code reference (2.16 to 2.17), member technical handbook (2.7 to 2.8), integration pack (3.4 to 3.5), conformance test plan (1.0 to 1.1), WebSocket quick guide, interactive console, documentation site. |
| 3.23 | 2026-08-07 | Lot fold bounds: a fix and a disclosure, following integration feedback. THE FIX: `positionSide` narrowing on `GET /v1/lots` now genuinely reduces the funding-attribution work. The refusal message for the attribution work bound has always said "narrow the query with positionSide", but the fold applied the filter only to the RETURNED lots while attributing funding across all three buckets, so a narrowed query did identical work and was refused identically. A filtered fold now processes only the requested bucket (buckets are independent stacks, so no returned figure changes) and starts its settlement count at that bucket's first fill. No wire shape changes; the only behaviour change is that narrowed queries which previously refused with `2013` now succeed, which is what the message promised. `/openapi.json` `info.version` moves 2.11.0 to 2.11.1. THE DISCLOSURE: section 6e now publishes the numeric bounds (100,000 fills per scope; 20,000 funding settlements, about 18 years of an 8 hour grid; 5,000,000 attribution steps), the crossing prediction, and the honest statement that past the settlements bound, or when a single bucket alone exceeds the work bound, there is no in-API remedy: reconcile by positions and executions, and the lot close endpoint is refused for such a scope too. Companion updates: complete code reference (KronosX-API-Codes moves 2.15 to 2.16, the 2013 row carries the bounds), interactive console, documentation site. |
| 3.22 | 2026-08-06 | Funding presentation, ADDITIVE on every surface. No field is removed or renamed, no default moves, no code changes meaning, and every request that worked at revision 3.21 works identically; `/openapi.json` `info.version` moves 2.10.1 to 2.11.0. The only reason to re-read is that funding was being misread, and the venue was not saying enough to prevent it. (1) **Rates are fractions, not amounts** (section 7). `fundingRate` has always been a dimensionless fraction of position notional PER INTERVAL, never a currency amount: `"0.0001"` means 0.01% per interval, so a 100,000 USD long pays 10 USD at each settlement. Nothing on the wire said so. Every surface publishing a rate now also publishes `fundingRatePct`, the same number scaled by 100, and `fundingRateAnnualizedPct`, rate x (365 days / `fundingIntervalHours`) x 100 with no compounding, for comparison against other venues: `GET /v1/funding/current` (which also gains `fundingIntervalHours`), `GET /v1/ticker`, `GET /v1/funding/history`, and the WebSocket `funding.{symbol}` channel on both its rate and settlement frames. The annualised figure is OMITTED, not null and never `0`, on a market with no funding grid, because a fabricated zero reads as a market that costs nothing to hold. (2) **The currency is named.** Funding PAYMENTS are the currency-denominated half, booked in the instrument's settlement currency, and settlement is a property of the INSTRUMENT rather than of the venue. `GET /v1/positions` and `GET /v1/positions/end-clients` rows now carry `settleCcy`, the currency their `unrealizedPnl`, `realizedPnl` and `accFunding` are expressed in, and the funding and lot surfaces carry it too. Today every listed market settles in USD; do not hard-code that. (3) **Funding on lots** (section 6e). `GET /v1/lots` and `POST /v1/lots/{lotId}/close` now report financing: each lot carries `accFunding`, what it has been charged since it opened, and `fundingIntervals`, how many settlements it has been open across. The venue charges funding on the client sub-position NET, once per anchor, never per lot, so the per-lot figure is an ATTRIBUTION of a booked charge; it is exact at each anchor, and the shares sum to the charge on the bucket net to the micro, with the rounding remainder booked to the largest lot exactly as the venue books it to the largest client. Financing follows the quantity actually held, so a partially closed lot keeps what it incurred while it was larger and a fully closed lot takes its financing with it. The envelope therefore publishes three figures and all three matter: `accFunding` is what the venue BOOKED and is the one to reconcile client statements against, `openLotsFunding` is what the listed lots carry, and `closedLotsFunding` is the difference. Same caution as per-lot realized PnL: the lot figures present a booked total, and only the booked total is what the venue settles with you. A client with both very long funding history and very many open lots can exceed the attribution work bound, refused with `2013` and a message telling you to narrow the query with `positionSide`. Companion updates: complete code reference (KronosX-API-Codes moves 2.14 to 2.15), member technical handbook (2.6 to 2.7), interactive console (`/openapi.json` 2.11.0), documentation site. |
| 3.21 | 2026-08-06 | A DOCUMENTATION revision with NO behaviour change: nothing is added, removed, renamed or repurposed on any surface, no request shape changes, every default is unchanged, and every request that worked at revision 3.20 works identically. `/openapi.json` `info.version` moves 2.10.0 to 2.10.1 as a documentation revision. It states explicitly what `reduceOnly` has always done, because the previous wording invited a wrong reading and has already cost a counterparty an integration cycle. **The reduce-only budget is judged against your MEMBER NET position on the instrument, and never against an end client's sub-position**, since venue risk logic never reads client sub-positions at all. The consequence a broker has to build for: an end client closing its OWN position while your member book leans the other way is refused with `2008` even though that client genuinely holds the position, and on a perfectly netted member book (member net exactly zero, say one client long 10 and another short 10) EVERY reduce-only order on that instrument is refused, from either client, on either side. `reduceOnly` is a MEMBER risk control, the venue-enforced guarantee that an order can only ever shrink your own house exposure, for example a risk desk flattening the member book during a margin call; it is NOT a client close tool. Send an end-client close as a PLAIN order (no `reduceOnly`) gated on that client's own row from `GET /v1/positions/end-clients`, which only you can do, since only you know your client-level book; that is also exactly why `POST /v1/lots/{lotId}/close` defaults `reduceOnly` to false. Now a warning in section 6 (the full statement), cross-referenced from the batch mass-close example in section 6c and from the lot close in section 6e, and made explicit in the margin preview's reduce-only budget rule in section 7, which measures the same member-level budget over the whole basket. Companion updates: complete code reference (KronosX-API-Codes moves 2.13 to 2.14, the 2008 row now carries the broker guidance), member technical handbook (2.5 to 2.6), interactive console (`/openapi.json` 2.10.1), documentation site. |
| 3.20 | 2026-08-05 | Multi-key API credentials, the sandbox operations policy and sandbox risk-tier alignment, in one revision. Nothing is removed or renamed, no request shape changes, and `/openapi.json` `info.version` is unchanged. (1) **Multi-key API credentials** (member request; sections 1, 3, 10.1, 10.6). A member now holds up to 5 API keys PER SCOPE (`TRADE` and `READ_ONLY`); issuing a key ADDS it and every existing key keeps working, where previously issuing a key of a scope replaced the member's existing key of that scope. Revocation is explicit and per key, from the member portal keys card or via venue operations, immediate on every transport, and ends live WebSocket and FIX sessions authenticated with the revoked key (on WebSocket the session drops back to unauthenticated with code 1001 without the socket closing); the member's other keys are untouched. Secrets are shown exactly once, at issuance. Rotate as a failover, not a cutover: issue the new key, migrate traffic, then revoke the old one. BEHAVIORAL NOTE: the two rate-limit buckets (reads, orders) are now keyed per MEMBER and shared across all of the member's keys, previously documented as "per API key". The defaults are unchanged (6000 reads/min, 40000 orders/min), per-member overrides via venue operations are unchanged, and cancels remain never throttled; a member running one key per scope sees no change, and a member issuing additional keys draws them all from the same two buckets rather than multiplying its budget. (2) **Sandbox operations policy** (new section 1a). Operator-ratified rules the venue enforces internally, restated for integration planning; communications ride the members Slack workspace, the member portal and this document. Full sandbox data resets are announced at least 5 business days in advance via the members Slack channel and a portal notice, and are scheduled together with breaking manual revisions where possible; a reset invalidates golden captures and standing test state, and the announcement names the post-reset baseline (manual revision, instrument set, standard test balances); API credentials survive a data reset unless the announcement says otherwise, and portal logins persist; margin top-ups for test members are available on request via the members Slack channel and are normally booked the same business day; per-member state resets (flatten positions, restore standard balances) are available on request and are executed in the next maintenance window; resting orders and other standing test state survive ordinary releases, only announced resets clear them. (3) **Sandbox risk tiers now match production** (section 1a) on every listed perpetual: initial margin 2 percent to 1 million USD notional, 5 percent to 5 million, 10 percent above, maintenance margin half of each. Two deliberate parity differences remain and are stated: the stale-mark timeout is 60 seconds on the sandbox (300 seconds in production) so the staleness ladder can be exercised quickly, and the `BTC-USD-KO` barrier market is sandbox-only for now. Production ISINs are pending DSB issuance. Companion updates: documentation site (overview, Trading, REST, WebSocket and FIX pages), member portal keys card wording. |
| 3.19 | 5 Aug 2026 | Order book deltas now carry window repair rows: after a delete, the levels sliding into the standard window boundaries (depth 1, 5, 25 and 100) are included in the same delta, so a depth-limited subscription never goes short a level and a depth 25 subscription verifies the top 25 checksum continuously (section 10.4). Previously a delete inside the window left the subscriber unable to compute the published checksum until natural refill. Repair rows apply like any other row; trim to your subscribed depth. |
| 3.18 | 2026-08-05 | Hardening batch: precision corrections to the revision 3.17 features plus a set of venue robustness changes, in one revision. Nothing is removed or renamed, no request shape changes, every absent-field default is unchanged, `/openapi.json` `info.version` is unchanged (no schema moves), the FIX rules of engagement revision moves 2.8 to 2.9 and the complete code reference (KronosX-API-Codes) moves 2.12 to 2.13. (1) **Queue position presence rule sharpened** (section 6). Presence of `queueQtyAhead`/`queueOrdersAhead` on the place ACK now means resting AT ACK TIME: when the same command that rested the order also terminated it (a trigger cascade filling the just-rested remainder, for example), the ack reports the terminal status with the queue fields CLEARED, so automation keying on presence never acts on an order that no longer exists. The `ORDER_ACCEPTED` event and the FIX accept report keep the rest-time figures. (2) **Lots, corrections and refusals** (section 6e). `mark` and `unrealizedPnl` are OMITTED, not null, while the market has no mark; lots are grouped by bucket, `NET` then `LONG` then `SHORT`, oldest first WITHIN each bucket (the bucket's FIFO consumption order), not globally age ordered; the fold is CLAMPED to the live sub-position, so venue-side closes (ADL, liquidation) never surface as phantom open lots and per-lot unrealized keeps summing exactly to the bucket total; the close response envelope `asOfSeq` is the ENGINE sequence of the close order's ack (order-entry semantics), while `GET /v1/lots` keeps the store watermark; BARRIER symbols are refused by both routes with `2013` (bilateral contracts have no lots); a lot close on an RFQ-mode market is refused with `2022` up front; a concurrent close of the same lot is refused with `2013` ("a close for this lot is already in flight"); `clOrdId` reuse on the close mirrors `POST /v1/orders` (different payload `2009` HTTP 400, identical payload restates the original with `duplicate: true` and records no assignment); the reserved `KX-` prefix rejects `2020`; and a history-store failure recording the assignment no longer fails the request, the view degrades to FIFO for that close. (3) **Cancel on disconnect refinements** (section 10.5a, FIX rules section 1). Only a TRADING-capable session counts: a READ_ONLY (drop copy) session or logon neither suppresses a pending cancel through the liveness probe nor disarms it, on WebSocket and FIX alike; the armed identity is captured at arm time, so a mid-session key rotation does not void an acknowledged arming; a venue restart voids all in-memory arming uniformly (documented policy: nothing fires during or after a restart, reconnects start unarmed); and "a second armed session dropping restarts the window" is now stated as BEST EFFORT, with the venue re-checking liveness immediately before submitting. (4) **WS private auth freshness bound** (section 10.1). `expiresMs` must lie between now and now plus 60 seconds by the venue clock (the REST recv-window cap); a larger value is refused with `1001` "auth failed: expiresMs too far in the future". Previously only past values were refused. (5) **REST signing covers the exact body bytes** (section 3). Chunked and HTTP/2 requests of unknown length are verified against the body bytes as they arrived (previously verified against an empty body, an undocumented interop break now fixed), non UTF-8 bodies are HMAC'd as raw bytes, and an explicit request-body ceiling of 1 MiB (1,048,576 bytes) answers HTTP 413 above it. Conformant clients see zero change. (6) **WS book channel admission and session ceiling** (section 10.2). The `{depth}` token in `orderbook.{depth}.{symbol}` and `matchableBook.{depth}.{symbol}` is validated against the published set 1, 5, 25, 100 on subscribe AND resync, refused with `2013` rather than clamped (previously unparsable tokens defaulted to depth 25); `resync` applies the same admission as `subscribe` instead of silently acking invalid arguments; and one session may hold at most 64 channels, a subscribe past the cap refused with `2013` "subscription limit reached (64 channels per session)". The REST and `get.*` `depth` parameter keeps its nearest-bucket behavior. (7) **Venue-close price cap disclosed** (section 7). In extreme conditions a venue-generated close (ADL, RFQ-market liquidation close, final settlement) executes at the nearest representable price rather than the raw bankruptcy or operator price, both legs at the same capped price, final settlement at one uniform effective price per instrument; no tradable position approaches the cap. (8) **Liquidation detection pauses on stale exposure** (sections 7, 10.9). Automatic liquidation detection pauses while any market the member holds a position, an open order or barrier exposure on is stale or unmarked, and the breach re-detects on the first sweep after recovery; an operator-forced unwind is not gated. (9) **Funding settles every elapsed anchor** (section 7). After an outage spanning N anchors, members receive one `FundingSettled` event and one ledger entry per missed anchor at the last sequenced rate and mark, at most 8 anchors per settlement with the remainder on subsequent timer ticks about 30 seconds apart, instead of a single collapsed charge; `GET /v1/funding/history` row counts change accordingly. (10) **Closed unwind episode retention** (section 7). `GET /v1/unwinds` and WS `get.unwinds` retain closed episodes up to 10,000 venue-wide, oldest released first; older history remains on the event stream, `GET /v1/adl/events` and your archives; open episodes are never released. (11) **No late staleness warning after the flip** (section 10.9). A restart or failover landing after a market already entered reduce-only emits no late `STALE_MARK_WARNING` for that episode, so the published warning-precedes-flip ordering holds across restarts; a restart before the flip still re-warns once. (12) **FIX session hardening and regulatory tags** (FIX rules of engagement 2.9). A connection not completing Logon within 10 seconds of TCP accept is closed (best effort Logout "logon timeout"; inbound bytes do not extend the deadline); the TLS handshake must complete within 5 seconds; concurrent-connection ceilings enforced at accept: 512 FIX connections total, 64 not yet logged on across all addresses, 8 such per remote address. Seven NEW venue tags on NewOrderSingle carry the remaining REST/WS `reg` object fields, recorded verbatim into the same per-order regulatory record: `9021` tradingCapacity, `9022` clientIdType, `9023` clientId, `9024` investmentDecisionId, `9025` executionDecisionId, `9026` dea (Y/N), `9027` liquidityProvision (Y/N); an absent tag leaves the field unset exactly like omitting the key on REST, validation is shared across transports (the 512 byte reg record ceiling rejects `2013`), and `35=G` accepts none of them (amend-only). Companion updates: FIX rules of engagement (revision 2.9), FIX integration manual, complete code reference (revision 2.13: code 2032 row and 404 grouping), documentation site (REST, WebSocket, FIX, Trading and Reference pages, including the 3.17 feature set that the 3.17 site build had not carried). |
| 3.17 | 2026-08-05 | Three member-requested features in one revision: queue position on the place ack, cancel-on-disconnect, and a per-client lot view. Everything is additive: no field, endpoint or code is removed, renamed or repurposed, no request shape changes on any existing surface, every absent-field default is unchanged, `/openapi.json` `info.version` moves 2.9.1 to 2.10.0 and the FIX rules of engagement revision moves 2.7 to 2.8. (1) **Queue position on the place ack** (section 6). When a placement RESTS an order, the ack carries `queueQtyAhead` (decimal string, quantity resting ahead at the order's price level in FIFO priority) and `queueOrdersAhead` (integer, orders ahead at that level); a first order at an empty level reports `"0"`/`0`, present, meaning front of the queue. PRESENCE is the signal that the order rested: the fields are OMITTED, not null, on full fills, IOC/FOK/market remainders, conditionals accepted as `PENDING_TRIGGER`, rejections, amends and order-history reads, so every pre-queue payload stays byte-identical. The figures are a snapshot at the moment of rest, measured after any reduce-only trim the same placement caused, and are immediately stale; the venue never updates them on the order. Carried on the REST place ack, the WS `order.place` ack and the `ORDER_ACCEPTED` frame of the `order` channel, and on FIX as venue tags `9017` (qty ahead) and `9018` (orders ahead) on the accept ExecutionReport (`150=0`) of a placement that rested, omitted otherwise. (2) **Cancel on disconnect** (new section 10.5a). Opt-in, armed PER SESSION and in memory only: the WS op `session.cancelOnDisconnect` (`{enabled, graceMs}`, grace clamped 0 to 30000, default 0; ack msg `armed graceMs=N` or `disarmed`; requires a trading-capable credential, READ_ONLY drop-copy keys answer `1005`) and, on FIX, Logon tag `9004=Y` with the grace in `9005`, echoed on the Logon reply and forced `N` on a drop-copy session. When an armed session drops and the member has no live authenticated session left on that transport after the grace window, the venue submits ONE member-scope cancel-all covering every open order of the member on every instrument regardless of originating transport; the cancels report `cancelReason` `USER_REQUESTED` (the armed protection is the member's own standing instruction). A reconnect that authenticates within the grace window disarms the pending timer, per transport: WS timers are cleared only by WS `auth`, FIX timers only by FIX Logon. ORDERS ONLY, deliberately: the venue never closes, reduces or hedges a position on disconnect. (3) **Lots** (new section 6e). A CFD-style display of an end client's open position as the opening fills that built it, folded on demand from the fill history per (`clientUid`, hedge bucket, instrument), requiring the venue's history store: every bucket-increasing fill opens a lot (`lotId` = the opening fill's `tradeId`, entry = its price), reducing fills consume explicitly assigned lots first then FIFO from the oldest, and a fill through zero closes everything and opens one opposite-direction lot with the remainder. `GET /v1/lots?clientUid=&symbol=&positionSide=` returns the open lots oldest first (`positionSide` filter `NET`/`LONG`/`SHORT`, absent = all buckets; envelope `asOfSeq` is the store watermark; at most 100,000 fills per scope, past that the fold refuses with `2013`). `POST /v1/lots/{lotId}/close` closes one lot: a MARKET IOC on the opposite side for the lot's remaining quantity, tagged with the lot's bucket, with an assignment recorded so the fold consumes THAT lot first; `2032` (HTTP 404, new registry code) when the `lotId` is not an open lot of that `clientUid` and symbol. The close is real order entry (order budget, order-to-trade regime, trading credential) and `reduceOnly` is applied only when sent, default false, because the reduce-only budget is MEMBER-level. Lots are a display convention: the engine keeps no lots, margin and liquidation and the venue's booked realized PnL stay average-cost per sub-position, per-lot unrealized sums exactly to the bucket's unrealized, and per-lot realized attribution is presentational, which is why the surface reports open lots only. REST only; no WebSocket op and no FIX surface for lots. Companion updates: FIX rules of engagement (revision 2.8: Logon tags 9004/9005, ExecutionReport tags 9017/9018, the order-entry tag table now lists 9012, conformance checklist), FIX integration manual, interactive console (`/openapi.json` 2.10.0), documentation site. |
| 3.16 | 2026-08-05 | Hedge mode reaches order history (REST only; no WebSocket or FIX change, no request shape change anywhere, and `/openapi.json` `info.version` moves 2.9.0 to 2.9.1). `GET /v1/orders/history` rows now carry the optional `positionSide` field (`LONG` \| `SHORT`), OMITTED, not null, on netting orders, exactly like the live order surfaces revision 3.15 shipped, so every pre-hedge history row stays byte-identical and the one order surface that did not yet echo the field now does. Also in this revision: this reference now documents the route itself, in the section 6 Manage orders table (closed orders AND rejections, the only place a rejection that never reached the book can be re-read after the fact, newest first, served from the projection store with the envelope `asOfSeq` as the store watermark, `limit` default 100 clamped to 1 to 1000); it had been documented in the REST integration manual and on the documentation site only, and the interactive console now describes the route and its row schema too. Companion updates: documentation site REST page, interactive console (`/openapi.json` 2.9.1). |
| 3.15 | 2026-08-05 | Hedge mode: parallel long and short client sub-positions (section 6, new subsection). REST, WebSocket and FIX surfaces all touched, one new OPTIONAL order field and its echoes, nothing removed or renamed, and every absent-field default unchanged: `/openapi.json` `info.version` moves 2.8.0 to 2.9.0 and the FIX rules of engagement revision moves 2.6 to 2.7. The new field is `positionSide` (`LONG` \| `SHORT`) on order entry: `POST /v1/orders`, every `POST /v1/orders/batch` entry, the WS `order.place` and `orders.batch` ops, and FIX NewOrderSingle via the new venue custom tag `9016` (values `LONG` \| `SHORT`, absent = netting, following the `9001`/`9002`/`9012` custom-tag convention). A tagged order's fills act on that BUCKET of the order's `clientUid` sub-position, so one client can hold a long and a short sub-position in the same instrument at once, at most one `LONG`, one `SHORT` and one netting sub-position per client per instrument; netting applies INSIDE the bucket, so a BUY tagged `SHORT` reduces the short bucket. An order WITHOUT the field keeps acting on the netting sub-position, bit for bit as before this revision: the field is strictly additive and the default is unchanged. Order-book perpetual markets only: `positionSide` on a spot instrument rejects `2016` (that code's registry row now names the field), an invalid value rejects `2013`, and RFQ and barrier flows do not take the field in this release. MEMBER-LEVEL MARGIN IS COMPLETELY UNAFFECTED: your member position stays the net sum over all buckets, risk never reads sub-positions, and hedged client flow nets at member level for margin exactly as it always did. Funding attribution, liquidation allocation and final settlement operate per sub-position exactly as they always did per client, bucket by bucket. `reduceOnly` composes with `positionSide` (the reduce-only budget stays member level). Echo contract: acks, open-order reads and order events carry `positionSide` back, OMITTED (not null) for netting orders, so every pre-hedge payload stays byte-identical; FIX ExecutionReports echo tag `9016` the same way, omitted on netting orders. `GET /v1/positions/end-clients` rows (and the WS `clientPosition` channel and `get.clientPositions` op) now carry `positionSide` (`LONG` \| `SHORT`, omitted on netting rows), one row per bucket. Companion updates: FIX rules of engagement (revision 2.7, order entry tag table, ExecutionReport tags, conformance checklist), documentation site REST, FIX and Reference pages (the venue custom tag table gains 9016), interactive console (`/openapi.json` 2.9.0). |
| 3.14 | 2026-08-05 | Client identity for transaction reporting (new section 6d). REST only: no WebSocket op, no FIX surface, no change to order entry or to any existing endpoint, and `/openapi.json` `info.version` moves 2.7.5 to 2.8.0. PM MTF Ltd owes transaction reports under MiFIR Article 26(5) for members that are not MiFID II investment firms; where such a member's trade carries client flow, the venue's report needs the underlying client's RTS 22 Annex I Table 2 identification. Three new routes carry that data, keyed by the `clientUid` you already stamp on every order and DECOUPLED from order entry: `PUT /v1/clients/{clientUid}/identity` (upsert and update mechanism in one; STRICT body like order entry, unknown field 2017; requires a trading-capable credential, READ_ONLY drop-copy keys answer 1005; every PUT appends an immutable audit revision and the response carries the monotonic `revision`), `GET /v1/clients/{clientUid}/identity` (the current record; 2031, HTTP 404, when none), and `GET /v1/clients/identity/missing?from=&to=&limit=` (the chase list: distinct clientUids of yours that traded on MiFIR-scope, that is perpetual, markets in the window with no record on file; default window the last 48 hours, `limit` 1 to 1000 default 500; the envelope `asOfSeq` is the projection watermark, so a trade acked a moment ago may not be visible yet). TRADING IS NEVER BLOCKED on identity data: the transaction report is due by close of the following working day, and the missing report is how the backlog is chased inside that window. The stored record carries `mifirId`, the identifier the venue's report will carry, resolved at submission time: the LEI for a legal entity, the national identifier for a natural person, or, when a natural person has none, the derived RTS 22 CONCAT code (country + date of birth + first five letters of each name, uppercased, diacritics folded per the ESMA transliteration table, `#`-padded), with the choice reported as `mifirIdSource` (`LEI` \| `NATIONAL_ID` \| `CONCAT`). A market maker can register its own LEI against its house UID so own-account sides resolve too. Members that ARE MiFID II investment firms report their own transactions and are entirely unaffected. Two new registry codes: `2030 CLIENT_IDENTITY_INVALID` (HTTP 400; the message names the offending field) and `2031 CLIENT_IDENTITY_NOT_FOUND` (HTTP 404). Deployments without PostgreSQL answer `5004`/503 on all three routes. Companion updates: complete code reference (revision 2.12), REST integration manual and Integration Pack, netting and unsolicited-flow statement (version 1.2, the end-client data sentence now carves out this surface), documentation site REST and Reference pages, interactive console (`/openapi.json` 2.8.0). |
| 3.13 | 2026-08-04 | The FIX surface only (FIX rules of engagement revision 2.6, sections 1, 2, 3, 4 and 5.1). No REST or WebSocket change of any kind, no new, removed or renamed field on any surface, and `/openapi.json` `info.version` stays 2.7.5, since the OpenAPI document does not describe FIX. Revision 3.12 published the rule that a field whose ABSENCE changes what a request MEANS is refused by name while a field whose absence selects a documented default stays optional. This revision completes it with the third clause, **a value you actually SENT is never silently replaced**, and applies the whole rule to order entry, where it also removes an outbound shape that was not valid FIX. (1) **`MarketDepth(264)` is no longer adjusted.** Previously `264=0`, which is FULL BOOK in FIX 4.4 and something this venue does not serve, was read as five levels, and any value above 25 was clamped to 25, in both cases with no reject and nothing on the wire to reveal it, so a member received a book of a different shape from the one they asked for and had no way to tell. From this revision a `264` that is PRESENT must be an integer from 1 to 25; `0`, a larger value, a negative, a non-numeric string and an empty value all answer `MarketDataRequestReject(35=Y)` with the NEW reason code `MDReqRejReason(281)=5` and a `Text(58)` naming the tag and the range, and nothing is subscribed. `281=5` is added rather than repurposed, so no existing reason code changes meaning. **An ABSENT `264` is unchanged and still means 5 levels a side**, so a request that never sent the tag is not affected at all; and the `263=2` disable is unchanged and still acts on `263` and `55` alone, so a disable that echoes back a `264` the venue would refuse on a subscribe is still accepted and still stops the stream. The two breaking shapes are therefore precisely these: a client asking for more than 25 levels (previously served 25) and a client sending `264=0` or a value that is not a positive integer (previously served 5). (2) **A required tag ABSENT on `NewOrderSingle(35=D)` is now refused by name at the session layer.** `Symbol(55)`, `Side(54)`, `OrderQty(38)` and `OrdType(40)` absent or empty answer a session level `Reject(35=3)` with `SessionRejectReason(373)=1`, `RefTagID(371)` naming the tag, `RefMsgType(372)=D` and a `Text(58)` naming it, decided BEFORE the order is built, so nothing reaches the engine and no ExecutionReport of any kind follows. Previously each answered an `ExecutionReport 150=8` carrying `9010` 2001, 2013, 2002 and 2004 respectively. Those venue codes are unchanged and continue to answer a PRESENT but invalid value, which is the far more common case: an unknown `Symbol(55)` is still 2001, an unsupported `OrdType(40)` still 2004, an off-grid `OrderQty(38)` still 2002, an unsupported `Side(54)` or `TimeInForce(59)` still 2013, and on a broker member an absent `Account(1)` is still 2010. A `35=3` carries no `ClOrdID(11)` and no `ExecID(17)`, so correlate it by `RefSeqNum(45)`, and do not leave your client waiting for a `150=8` after one. A message refused at the session layer was never accepted for processing, so this does not weaken the published guarantee that every NewOrderSingle the venue ACCEPTS FOR PROCESSING is answered exactly once. Where a required tag is absent AND the request would also have been rate limited, blocked by the order-to-trade regime or refused for using the reserved `KX-` namespace, the `35=3` wins, because the tag check is decided first. Because it is decided first it also reaches neither meter: a message refused at the session layer spends none of your order budget and is not counted as an order message in your RTS 9 order-to-trade ratio, since the venue never accepted it for processing. Everything answered with a BUSINESS reject (`35=8`, `35=9`) is charged and counted exactly as before, which includes an unknown `Symbol(55)`, an unsupported `OrdType(40)`, a `35=G` naming no order and a `35=F` identifying none; the reserved `KX-` 2020 reaches neither meter on FIX, REST or WebSocket alike, unchanged. This is the only accounting difference between FIX and the native transports, which have no session layer to refuse at, and it cannot enlarge an effective budget because a refused message places no order. (2a) **One value-level refusal joins them, `Side(54)` outside the FIX 4.4 enumeration.** `Side` is the single tag the venue echoes onto its own answer that FIX both marks required on an ExecutionReport and restricts to a fixed value set (`1` to `9`, `A` to `G`). A `54=X` echoed back produced a `35=8` that a dictionary-validating client engine refuses outright, so the member never saw the `9010` they needed, and neither omitting `54` nor substituting a Side you did not send is available. From this revision an out-of-enumeration `Side(54)` answers a session level `Reject(35=3)` with `SessionRejectReason(373)=5` (value is incorrect for this tag), `RefTagID(371)=54` and `RefMsgType(372)=D`. **A Side that IS in the enumeration but that the venue does not trade, `54=5` sell short for example, is NOT this case and keeps its unchanged business reject 2013**, because the report echoing it is valid FIX; and `Symbol(55)` is not an enumerated field, so an unknown symbol is still echoed verbatim with 2001. (3) **The venue no longer emits a zero-length value for any tag on any outbound message.** A gateway built reject echoed `ClOrdID(11)`, `Symbol(55)` and `Side(54)` "as you sent them", so a tag you omitted came back as `11=`, `55=` or `54=` with nothing after the equals sign, which is not valid FIX and which a strict client engine rejects in turn: your diagnostic for a malformed order was itself malformed. This is the same defect fixed for `MDReqID(262)` in revision 3.12. `55` and `54` can no longer be empty on a `35=D` answer because item 2 refuses them earlier. **`ClOrdID(11)` stays OPTIONAL on `35=D`, matching the native API, so nothing about accepted input narrows here**: an order sent without a reference is still accepted, still works, and still has no duplicate protection and no cancel or replace by `OrigClOrdID(41)`. What changes is that tag 11 is now ABSENT from that order's ExecutionReports, on every one of them (accept, fill, cancel, expiry, replace, engine reject, gateway reject and the `150=I` restatement), instead of present and empty. FIX 4.4 marks `ClOrdID` not required on an ExecutionReport, so omitting it is conformant where a zero-length value is conformant on no reading. This SUPERSEDES the sentence in revision 3.12 that said such an order's reports carry an empty `11`. `Account(1)` is omitted rather than sent empty on the one report where the venue has no identifier, which is the 2010 rejection that says the identifier was missing, and `Symbol(55)` is omitted rather than sent empty on the rare rejection whose market was deleted while the order was in flight. The promise now covers the STANDARD HEADER too, which the body-level rule could not reach: `SenderCompID(49)` and `TargetCompID(56)` are required on every FIX message and can be neither omitted nor left empty, yet the venue carried no CompID at all until a Logon named one, so its own "logon required" Logout, the first frame a misconfigured client ever receives, went out with a zero-length `56=`; and a Logon carrying `49=` (present, empty) authenticated normally and then put a zero-length `56=` on every frame of that session for its whole life. **An absent or empty `SenderCompID(49)`, and any answer the venue has to send before your Logon arrives, is now addressed as `TargetCompID(56)=CLIENT`.** `49` is not narrowed by this and stays optional, since its absence selects how the venue addresses you rather than changing what your Logon means; send a real CompID. (4) **`OrderCancelReject(35=9)` reports a reference you did not send as `KX-NONE`.** FIX 4.4 requires `OrderID(37)`, `ClOrdID(11)` and `OrigClOrdID(41)` on this message, so they can be neither omitted nor left empty, while the venue deliberately keeps accepting a cancel identified by `OrderID(37)` alone and a replace that never carries `41`. Those two used to go out zero-length and now carry the sentinel; `OrderID(37)` keeps its published `0` sentinel. **All three read a tag you sent EMPTY as one you did not send.** That is the everyday shape, a client library that always writes `37`, `11` and `41` and blanks the ones it does not hold: such a request used to be answered with a `35=9` that had lost tag `37` altogether, which is a message your own session layer refuses, so your cancel was answered with something you could not read. From this revision it carries `37=0`, `11=KX-NONE` and `41=KX-NONE`, all three PRESENT, on both the `35=F` and the `35=G` reject paths. `KX-` is the venue reserved namespace, refused with 2020 on order entry, so `KX-NONE` can never collide with a reference the venue would accept: do not match it against your own order table. A `35=F` carrying NEITHER identifier keeps `CxlRejReason(102)=1` and its `Text(58)` now names the two tags instead of describing an order the request never identified. (5) **A `TestRequest(35=1)` with no `TestReqID(112)`** used to be answered with a Heartbeat carrying a zero-length `112=`; it now answers a session level `Reject(35=3)` with `373=1`, `371=112`, `372=1`, and no Heartbeat. A message carrying no `MsgType(35)` at all used to answer a `Reject(35=3)` with a zero-length `372=`; it now answers `373=1` with `371=35` and no `372`. (6) **A `MarketDataRequestReject(35=Y)` is guaranteed-class** like the session level `Reject(35=3)` beside it. It was best effort, so back-pressure could drop it and leave you with an unanswered `MarketDataRequest`, no subscription, and a hole in `MsgSeqNum(34)` as the only trace, which this document tells you to read as "reconnect" rather than as an answer. A session too far behind to receive a refusal is now disconnected instead, which is the documented slow consumer policy already applied to your ExecutionReports and to the subscribe-time `35=W`. Only the refresh stream (`35=W`, `35=X`) remains best effort. **Unaffected in this revision:** every REST and WebSocket surface; every FIX order that sends all of `55`, `54`, `38` and `40` with a `54` that is a FIX 4.4 Side value, which is every conformant order; every FIX session that logs on with a non empty `SenderCompID(49)`; every market data request that omits `264` or sends it inside 1 to 25; the `263=2` disable; `TimeInForce(59)` absent still meaning GTC and `59=0` still rejected 2013; `Account(1)` still optional for a market-maker member; `MDUpdateType(265)` and the `NoMDEntryTypes(267)`/`MDEntryType(269)` group still optional with their documented defaults; and every venue code in `9010`, none of which changed number or meaning. Companion updates: FIX rules of engagement (revision 2.6), FIX integration manual, Integration Pack, the FIX page of the documentation site. |
| 3.12 | 2026-08-04 | One narrowing on the FIX surface only (FIX rules of engagement revision 2.5, section 5.1). No REST or WebSocket change of any kind, no new, removed or renamed field on any surface, and `/openapi.json` `info.version` stays 2.7.5, since the OpenAPI document does not describe FIX. **`MarketDataRequest(35=V)` no longer supplies a value for a required field it was not sent; it rejects, by name.** Previously an absent `SubscriptionRequestType(263)` was read as `0` (snapshot once), so a request that meant to subscribe received one `MarketDataSnapshotFullRefresh(35=W)` and then permanent silence, with no reject and nothing to distinguish it from a subscription on an instrument that never trades; an absent `MDReqID(262)` was read as the empty string and the venue then put a zero length `262=` on every snapshot and increment for that subscription, which is not valid FIX; and an absent `Symbol(55)` was reported as an unknown symbol with a blank name. From this revision, `262`, `263` and `55` are required on `35=V` and are rejected when absent or empty: `263` answers `MarketDataRequestReject(35=Y)` with `MDReqRejReason(281)=4`, `55` answers `281=0`, both with a `Text(58)` naming the tag, and `262` answers a session level `Reject(35=3)` with `SessionRejectReason(373)=1`, `RefTagID(371)=262` and `RefMsgType(372)=V`, because a `35=Y` must itself carry the `MDReqID` that is missing. The session stays up in every case and nothing is subscribed. `281=4` and `281=0` therefore now also mean "missing", which is the only change to an existing code's meaning. The requirement covers the `263=2` disable too, and that is the one shape where a refusal leaves something running: the venue drops a subscription by `Symbol(55)`, so a disable that omits `262` used to work, is now refused with the same session level `Reject(35=3)`, and **your subscription keeps delivering**. If your unsubscribe path keys off the symbol alone, add `262` to it before this revision reaches you. **Nothing else on `35=V` became required.** `MarketDepth(264)`, `MDUpdateType(265)` and the `NoMDEntryTypes(267)`/`MDEntryType(269)` group stay optional and keep their documented defaults (5 levels a side, conflated full refresh, all three entry types), because their absence selects a default rather than deciding whether a feed exists at all: a request carrying only `262`, `263`, `146` and `55` behaves exactly as it always has. Two clarifications of unchanged behaviour in the same revision, both prompted by the same report: **BODY field ORDER on a FIX message is not significant** (the venue reads every body field by tag, so `264` before `265` and `265` before `264` are the same request, and only the `269` group is read in arrival order; the standard header and trailer stay positional as FIX 4.4 requires, meaning `8=FIX.4.4` first, `9=` immediately after it and `10=` exactly at the `BodyLength` boundary, and a tag sent twice in one message keeps its first occurrence), and the FIX order entry defaults are now stated explicitly rather than left to be discovered, namely that an absent `TimeInForce(59)` means GTC while `59=0` (the FIX DAY default) is rejected `2013` because the venue does not offer DAY, that a market maker may omit `Account(1)`, and that `ClOrdID(11)` is not enforced, matching the native API, so an order sent without one has no duplicate protection and cannot be canceled or replaced by `OrigClOrdID(41)`. Companion updates: FIX rules of engagement (revision 2.5), the FIX page of the documentation site. |
| 3.11 | 2026-08-04 | One narrowing, on how long the venue remembers a `clOrdId`. No new, removed or renamed field on any surface, no request body change, and `/openapi.json` `info.version` is now 2.7.5. **The idempotency record for an order reference is now bounded by count as well as by age: the venue retains the most recent 4,000,000 references venue-wide, or 24 h, whichever is reached first.** Your own obligation is unchanged (a reference must still be unique per member per 24 h) and so is every behaviour inside the horizon: an identical resend still returns the original order with `"duplicate": true`, and a different payload on a live reference is still `2009`. What changes is only what happens to a reference the venue has RELEASED, and only on a day whose venue-wide order count is large enough to reach the bound before 24 h elapses: such a reference behaves exactly as one that aged out past 24 h always has, so resending it places a new order instead of returning the original, and reusing it with a different payload no longer rejects. A reference whose order is still working or pending trigger is never released, so cancel and replace by `clOrdId` are unaffected for the whole life of the order, including past 24 h (previously such a reference COULD age out from under a long-resting order). Retry loops, which act within seconds, are unaffected; reconcile long-running state by `orderId` or `GET /v1/orders` rather than by resending. Behind this: the retention index is engine state that a snapshot has to carry, and its size scaled with the venue's daily order count rather than with the number of members or clients, which is what took snapshot production past a platform limit on 4 August. Also in this revision, from the same review: **barrier markets gain a count limit alongside their size limits** (section 6b), at most 256 open contracts per member across all barrier markets and 4096 per market, refused on accept with `2011` for your own limit and `3007` for the quoting member's, the same codes the size caps already answer with. It bounds the work each open contract adds to every margin calculation and every price tick; it is not a risk limit, it releases as contracts knock out, expire or settle, and no member is within two orders of magnitude of it. And **one new error code, 2029 CLIENT_LIMIT**, for the same reason one level down: the venue's table of end-client identifiers also only ever grew. It is now capped at 1,048,576 distinct identifiers venue-wide, against about 50,000 in use across all members today, and an order or an RFQ acceptance that would have to register an identifier the venue has NEVER seen is refused with 2029 (HTTP 400) once the table is full. Orders quoting an identifier the venue already knows are never affected, nothing is deleted to make room, and no member is within an order of magnitude of the limit; it exists so that a client-identifier generator gone wrong is refused at the door instead of growing venue state without end. Companion updates: complete code reference, FIX rules of engagement, REST integration manual and Integration Pack, documentation site. |
| 3.10 | 2026-08-04 | `POST /v1/margin/preview` (and the WebSocket `get.marginPreview` op) finishes the order-entry parity revision 3.6 began, in the one admission rule it did not mirror and in the codes it answers with. No new, removed or renamed field on any surface and request bodies are unchanged; `/openapi.json` `info.version` is now 2.7.4. (1) **`accepted` now answers the reduce-only budget.** A `reduceOnly` order may only close what you hold: the venue admits one while the reduce-only quantity already working on that market, plus the new order, stays within your open position on the reducing side, and rejects the surplus with `2008`. The preview did not apply that rule at all, so a reduce-only basket that over-closes (two `SELL` 100 `reduceOnly` entries against a 100 lot long, say) answered `accepted: true` while sending it would have placed the first entry and rejected the second, in exactly the margin call the reduce-only exemptions exist to serve. Your entries are now measured together against what is left to close, so the answer does not depend on the order you send them in, and an entry on the side that would ADD to your position, or on a market where you hold nothing, is reported rejected. The reduce-only exemptions themselves are unchanged: exempt from the available gate, from `minNotional` and from the position cap, not exempt from the per-order size cap. What `accepted` still does not answer is venue state that is not a property of your basket: halts, member or instrument status, and whether a market order would find liquidity. (2) **An entry the venue would refuse outright now answers with the same code order entry answers**, instead of a blanket `2002` or a bare `accepted: false`: `2016` for `reduceOnly` or a market-type conditional BUY on a spot market, `2013` for an out of range trigger price or post-trigger limit price, `3005` for a market with no sequenced mark yet, `2002` for a limit priced at zero. `2017` and `2018` are unchanged. The basket answers with the first such entry. Admission gates are unchanged and still answer `accepted: false`, which is what `accepted` is for; only the checks order entry makes BEFORE admission became codes. (3) An entry that fails the per-order size cap no longer contributes to the reported `im`, `mm` and `available`: that cap is the bound that makes those figures computable at all, so they now describe the entries that could actually be staged, with `accepted: false` exactly as before. Companion updates: documentation site REST and trading pages. |
| 3.9 | 2026-08-04 | Halt reporting corrected and one liquidation edge closed, continuing revision 3.7. No new, removed or renamed field on any surface, and `/openapi.json` `info.version` is unchanged by this revision. (1) **A `CIRCUIT_BREAKER` stop that carries no `expectedResumeTs` keeps reporting `CIRCUIT_BREAKER`** (section 6, section 10.9). Revision 3.7 introduced breaker halts with no resume time, the ones raised while a market is in `REDUCE_ONLY` or `POST_ONLY`, and a venue restart re-derived those as `ADMINISTRATIVE` on the `venueEvents` SNAPSHOT and on `GET /v1/status`, with no later message correcting it. Since this document tells you `ADMINISTRATIVE` means the halt was raised by venue operations, that reading was wrong in the one direction that matters. The reason a market carries now survives a restart, and `ADMINISTRATIVE` again means exactly what it says. Read `reason` and `expectedResumeTs` independently: the reason tells you who stopped the market, the resume time tells you whether it ends by itself. (2) **Venue operations taking over such a halt is now published**, completing item 3 of revision 3.7: the takeover republishes `STOP_TRADING` with `reason: "ADMINISTRATIVE"` and `expectedResumeTs: null` for a breaker halt whether or not a resume time had ever been advertised for it. Previously the takeover of a no-resume-time halt was silent and you kept a `CIRCUIT_BREAKER` reason the venue knew to be stale. (3) **One exception to "venue-initiated closes stop at the halt"** (section 6). A breaker halt with no resume time ends only when venue operations end it, so a member already at or past bankruptcy with no other market left to work is closed by auto deleveraging at the bankruptcy price on that suspended market, instead of being carried until a person lifts the halt. If you hold the opposite side you may therefore receive an `ADL` on a market reported as `HALTED`; the payload is the ordinary one and the `episodeId`, price and qty fields are unchanged. Nothing else trades there: the book stays closed and your own orders are still rejected. Every other suspension still blocks venue-initiated closes outright, including an operations halt and a breaker halt that carries a resume time. (4) **The terminal `cancelReason` of a `FOK` the venue refuses whole is `FOK_UNFILLABLE` on both paths revision 3.7 changed**, named here because that revision described the outcome without naming the code: a perpetual `FOK` whose settlement would take a position past the venue exact-money range now reaches you as `FOK_UNFILLABLE` with no executions, where it previously filled part way and then reported `POSITION_SIZE_LIMIT`; and a conditional spot `FOK` whose base asset left your account between acceptance and trigger now reaches you as `FOK_UNFILLABLE` with nothing delivered, where it previously delivered what was still funded and then reported `SPOT_UNFUNDED`. Both codes are unchanged on every other order type, where they still arrive as venue-initiated cancels, and neither condition is reachable at any tradable size or on any ordinary funding shortfall. Companion updates: the trading page of the documentation site. |
| 3.8 | 2026-08-04 | FIX only, no REST or WebSocket change, so `/openapi.json` `info.version` is unchanged by this revision (the OpenAPI document does not describe FIX). One correction to a published rule and four corrections to behaviour, all where reject code `5005 COMMAND_FAILED` met the reply-completeness guarantee. (1) **A rule the venue does not hold has been withdrawn.** Revision 3.5 published "exactly one report ever carries a terminal `OrdStatus(39)` for a given order". That is not true and never was: `ExecType(150)=I` is a RESTATEMENT that repeats the state of the order it names at the moment it is built, so it carries a terminal `39` whenever that order is already terminal, which happens on the ordinary reused-reference answer for an order you have since canceled (`150=I`, `39=4`, after the `150=4` that closed it) and on a 5005 restatement of an order the same command also closed. The rule the venue does hold, now published in its place: exactly one report with a LIFECYCLE `ExecType(150)`, that is `150` other than `I`, ever carries a terminal `OrdStatus(39)` for a given order, and a `150=I` never closes an order. If you implemented the withdrawn sentence as a conformance assertion, exclude `150=I` from it; no venue behaviour changed for this item, only the description of it. (2) An order the engine ACCEPTED and then REJECTED inside the same command, which is what a post-only order that would cross and the `REJECT_TAKER` self-trade policy both produce, no longer draws a second report when that command then hits a 5005. Previously you received the fan-out's terminal rejection naming `OrderID(37)=0` AND a `150=I` restatement naming the real order id, for one `ClOrdID(11)`, contradicting the taxonomy revision 3.5 published. The engine's own rejection with the real reject code is the whole answer, exactly as when the order was rejected before it was ever accepted. (3) A reused `ClOrdID(11)` with a DIFFERENT payload keeps its real 2009 answer when the command then hits a 5005. Previously the 5005 replaced it with a `150=I` restatement of the original order carrying `9015=Y`, which says "reused reference with the byte-identical payload" and was therefore wrong on a payload that was not identical. Venue tag `9015` continues to mean exactly that and nothing else. (4) On a cancel (`35=F`) or a Cancel/Replace (`35=G`), a 5005 alone is no longer answered with an `OrderCancelReject(9)`. An `OrderCancelReject` means the venue REFUSED your request and the order is unchanged; when the cancel or the replace took effect and the command failed afterwards, the answer is the ordinary `ExecType(150)=4` or `5` and nothing else. A reject carrying `9010=5005` now means, precisely, that the request did not take effect. Previously such a cancel drew both the truthful `150=4` and an `OrderCancelReject` saying it had been refused, so a member reading the reject believed it still held exposure it did not hold. (5) `ExecID(17)` on gateway-built rejects (rate limit, engine busy, unknown symbol, unsupported `OrdType(40)`, reserved `KX-` reference, the 2009 rejection) is now unique within a session, as it already was on the `150=I` restatements. Previously two rejects built inside the same millisecond, ordinary when a client pipelines orders that are all refused, carried identical `ExecID`s, which a strict FIX client treats as a duplicate. Gateway `ExecID`s remain gateway-local values outside the `{engineSeq}-{eventIndex}` cursor space: correlate them by `ClOrdID(11)`. (6) The `150=I` restatement is now built and released on the venue's post-durable path rather than after it, so it can no longer arrive after a fill or cancel of the same order while describing the state before it. One cross-transport difference to note, unchanged by this revision: where the engine rejected an order and the command then failed, REST and the WebSocket report `rejectCode` 5005 while FIX reports the engine's more specific reason. Both describe the same command and the same outcome. A FIX client that never meets a 5005 and never reuses a reference sees nothing new in this revision. Companion updates: FIX rules of engagement (revision 2.4, sections 3, 4, 4.1 and the conformance checklist), FIX integration manual, documentation site FIX page. |
| 3.7 | 2026-08-04 | Halt semantics completed, continuing revision 3.1. No new, removed or renamed field on any surface, and `/openapi.json` `info.version` is unchanged by this revision. (1) **The circuit breaker is armed in every state in which a market can print**, not only while it is fully trading (section 6). A market in `REDUCE_ONLY` or `POST_ONLY` still prints, and the same band now applies there; previously the breaker was inactive on those markets, which are exactly the markets venue operations put under restriction when conditions are difficult. A breaker halt raised out of one of those states carries NO `expectedResumeTs` and does not end by itself: ending it automatically would return the market to full trading rather than to the restricted session it interrupted, so venue operations lift it and restore that session. Treat `expectedResumeTs` as present-or-absent rather than always present on a `CIRCUIT_BREAKER` stop; it was already documented as nullable and is null more often now. (2) **Venue-initiated closes stop at the halt boundary too** (section 6). A liquidation being worked through the book stops when the market halts, including when it is the liquidation's own trade that trips the breaker, and the remainder is worked on the first step after trading resumes. The part not yet closed stays your position, stays margined and stays inside the open liquidation; previously the rest of that slice could print into the market the venue had just halted. If you are the counterparty of a liquidation, expect fewer `LIQUIDATION` executions in the instant a market halts and the balance of them after it resumes. (3) **A `STOP_TRADING` may now be re-issued for a market that is already stopped** when its `reason` changes (section 10.9). The one case is venue operations taking over a halt the breaker had raised: the message repeats the current `tradingState` with `reason: "ADMINISTRATIVE"` and `expectedResumeTs: null`, and supersedes the earlier message for that market. Previously no message was sent and you kept a resume time the venue would not keep. Handle `STOP_TRADING` for a market you already hold as stopped by replacing your stored reason and resume time, not by ignoring it; `GET /v1/status` agrees with the latest message at all times. (4) One order-handling correction with no wire shape change: a `FOK` order can no longer fill partially in the extreme case where settling a fill would take a position past the venue's exact-money range (the `POSITION_SIZE_LIMIT` condition of revision 3.0). Such a `FOK` is now rejected whole, as `FOK` always promised, instead of filling the part that fit. No tradable size approaches this. Companion updates: the trading page of the documentation site. |
| 3.6 | 2026-08-04 | `POST /v1/margin/preview` (and the WebSocket `get.marginPreview` op) now answers what SENDING the same basket would do, correcting three ways in which it did not. (1) A `MARKET` entry is valued at the current mark on every market kind. A perpetual market entry always was; a spot market BUY was valued at ZERO, so its prefunding need disappeared and a basket the venue would reject for insufficient funds came back `accepted: true`. The mark is the price the venue already values a market order at for the per-order size cap and the `minNotional` floor. It is not the price a market order would get: the protection collar forms from the book at the instant of entry, so a market entry is an indication at today's mark, not a quote. Size a market order against the book, or send a limit. (2) `reduceOnly` entries are no longer held to the available gate. Order entry deliberately skips that gate for a risk-reducing perpetual order, so previewing a reduce-only basket during a margin call answered `accepted: false` at exactly the moment the answer mattered most; it now answers `true`, as sending it does. `reduceOnly` stays exempt from the `minNotional` floor and from the position cap, and stays subject to the per-order size cap, entry by entry, exactly as order entry applies them. (3) `accepted` now also answers the per-order size cap (2013), the `minNotional` floor (2028) and the position cap (2011) over the whole basket, so a basket that would be rejected on size is reported as rejected rather than accepted. What `accepted` still does not answer is venue state that is not a property of your basket: halts, member or instrument status, the reduce-only budget, and whether a market order would find liquidity. The served response schema for the endpoint still described the figures as the FIRST entry's, contradicting the basket pricing revision 3.3 shipped; it now describes the basket. No field is added, removed or renamed on any surface, request bodies are unchanged, and the preview remains a read: nothing is placed, nothing is reserved and no order budget is consumed. Separately, and not reachable at any balance the venue will credit: `equity` and `available` on `GET /v1/account` now saturate at the venue's exact-money ceiling (1e18 settlement-currency units, the ceiling a single order's notional already carries) rather than at a lower internal bound, so a figure inside the range the venue represents exactly is never returned, or admitted against, as a clamped value. `/openapi.json` `info.version` is now 2.7.3. Companion updates: documentation site REST and trading pages. |
| 3.5 | 2026-08-04 | FIX only, no REST or WebSocket change, so `/openapi.json` `info.version` is unchanged by this revision (the OpenAPI document does not describe FIX). Two corrections where reject code `5005 COMMAND_FAILED` met the reply-completeness guarantee revision 3.4 shipped. (1) A `NewOrderSingle(35=D)` whose command hit a 5005 used to receive TWO ExecutionReports: the truthful accept from the drop-copy fan-out, and then a terminal `150=8`, `39=8` carrying `OrderID(37)=0` and `CumQty(14)=0` for the same `ClOrdID(11)`, while the order was really resting or partially filled. FIX 4.4 allows exactly one terminal state per order, so a client that trusted the terminal report believed it held nothing and could double send or leave a position unmanaged. The venue now answers a 5005 with exactly one report, and that report tells the truth, matching what 5005 has always meant on REST and the WebSocket (the rest of `data` reports what the command DID achieve, so an `orderId` there is a real order): when the command created or matched an order, that order is RESTATED as `ExecType(150)=I` (order status) carrying its real `OrderID(37)`, `OrdStatus(39)`, `LeavesQty(151)` and `CumQty(14)` with `9010=5005` and the reason in `Text(58)`, and because a restatement is a status report rather than a lifecycle transition the order's single terminal report still arrives later when it fills, cancels or expires; when the engine rejected the order and only then failed, the engine's own rejection with the real reject code is the whole answer and no 5005 report is added over it; when nothing was created and nothing rejected, the answer stays the single `150=8`, `39=8` with `9010=5005`. Tag `9015` stays exactly what revision 3.4 defined, a reused reference and nothing else, so an ordinary 5005 restatement is marked by `9010=5005` alone and carries no `9015`; only a request that was BOTH a reused reference and a 5005 carries both tags on its one report. Venue tag `9013` is unrelated (it is the market data aggressor side on `35=X` trade entries) and is not used on any ExecutionReport. (2) The `150=I` restatement that answers a reused `ClOrdID` with an identical payload is now delivered on the guaranteed class: it is the whole answer to a NewOrderSingle, so under back pressure the venue ends the session rather than dropping the report and leaving the request outstanding on a connection it still considers healthy. Delivery only: the restatement is still gateway built with a gateway `ExecID(17)`, still outside the durable event stream and still absent from `GET /v1/stream/events`. No REST or WebSocket payload, field or code changes, and a FIX client that never meets a 5005 and never reuses a reference sees nothing new. Companion updates: FIX rules of engagement (revision 2.3, new section 4.1, ExecutionReport tags, conformance checklist), FIX integration manual, complete code reference (5005 and 2009 rows), documentation site. |
| 3.4 | 2026-08-04 | FIX only, no REST or WebSocket change. Every `NewOrderSingle(35=D)` the venue accepts for processing now receives exactly one terminal reply, closing a gap where reusing a `ClOrdID(11)` produced no ExecutionReport at all and left the request outstanding for the life of the session. The clOrdId registry verdict is the one order-entry outcome that is not an order lifecycle event, so it was never carried by the drop-copy fan-out that emits accepts, fills, cancels, amends and engine rejections; REST and WebSocket always answered it on the response and the op ack respectively. Both branches now answer on the session that sent the order, and both remain post-durable: a reused reference with a DIFFERENT payload gives `150=8`, `39=8`, `9010=2009` and `Text(58)=duplicate clOrdId with different payload`, the same registry rejection REST returns as HTTP 400 and the WebSocket as a `success: false` ack; a reused reference with the byte-identical payload is the idempotency path, so nothing is created and the ORIGINAL order is restated as `ExecType(150)=I` (order status) carrying the new venue tag `9015=Y`, that order's `OrderID(37)`, `OrdStatus(39)`, `LeavesQty(151)` and `CumQty(14)`, the FIX shape of the REST/WebSocket `duplicate: true` response. Neither answer is a lifecycle event: like a pre-engine rejection, neither is written to the durable event stream, neither is replayed by `GET /v1/stream/events`, and their `ExecID(17)` is a gateway-local value outside the cursor space, so correlate both by `ClOrdID(11)`. `ExecType(150)=I` and tag `9015` are additive: a client that has never reused a reference sees neither. `/openapi.json` `info.version` is 2.7.2 (this revision changes no REST or WebSocket surface). Companion updates: FIX rules of engagement (revision 2.2, new clOrdId reuse section, ExecutionReport table, conformance checklist), FIX integration manual, complete code reference (2009 row), documentation site. |
| 3.3 | 2026-08-04 | `POST /v1/margin/preview` (and the WebSocket `get.marginPreview` op) now prices the `orders` array as ONE BASKET. Previously the figures returned described the first entry only: additional entries were validated but moved nothing, the published documentation said so, and a member sizing a multi-order basket had to preview one order at a time. Now every entry participates: perpetual entries fold into order margin together, with the same one-sided max(buy, sell) netting per instrument that resting orders receive, so two same-side entries on one instrument add while a buy and a sell there cost only the larger side; spot entries have their prefunding needs summed per currency (quote cash for buys, base-asset units for sells); `accepted` answers every gate at once, and the result is independent of array order. `im`, `mm` and `available` describe the first entry's settlement currency (USD on every current listing). Single-entry previews return exactly what they always did. New cap: at most 50 entries per preview, the batch limit, above which the request rejects with 2002 on both transports; the interactive console gains a two-order basket example that was deliberately withheld while the venue did not price baskets. `/openapi.json` `info.version`  is now 2.7.2 Companion updates: Technical Handbook, REST integration manual and Integration Pack, documentation site REST and trading pages. |
| 3.2 | 2026-08-04 | Wire alignment, `reduceOnly` echo, bringing every event-derived order payload in line with the contract this document already stated (the batch response example in section 6c has always shown `reduceOnly: true` echoed on the ack). Place and cancel acks (REST and WebSocket, single and batch), WS `order` frames (including `ORDER_AMENDED` and `ORDER_REJECTED`), terminal-order reads and newly stored stream backfill rows (`GET /v1/stream/events`) now echo the order's actual `reduceOnly` flag; previously every event-derived payload reported `reduceOnly: false` even on a reduce-only order, while state reads (`GET /v1/orders/open`, `GET /v1/orders/{orderId}` for a working order) already reported it correctly. Enforcement was never affected, only the echo was wrong; no field is added or removed and requests are unchanged. Stream rows stored before this revision keep their stored payload and may still show `false`; treat state reads as authoritative for the flag on historical rows. `/openapi.json` `info.version` is now 2.7.1. |
| 3.1 | 2026-08-04 | Halt semantics stated explicitly in section 6's volatility safeguards, and enforced by the engine as stated. No wire change: no new, removed or renamed fields on any surface, and `/openapi.json` `info.version` is 2.7.0, unchanged by this revision. (1) Conditional orders do not trigger while a market is halted, now including a halt raised mid-cascade by the trades that would have triggered them: the stops still pending when the circuit breaker fires stay `PENDING_TRIGGER` rather than activating into the halted market, and they are evaluated again on the first qualifying price after trading resumes, which can be materially different from the price that would have triggered them. Previously such a stop could activate and fill into the halted market within the same engine command. Resting orders, working orders and every other order state are unaffected. (2) Only the automatic circuit-breaker halt ends by itself, and only it carries `expectedResumeTs` and the `CIRCUIT_BREAKER_EXPIRY` resume reason; a halt raised by venue operations now stays in force until venue operations lift it, and an operations resume of a breaker halt is final rather than a pause in the published cool-down. Members whose reconnect logic reads `GET /v1/status` should note that an operations halt no longer reports `CIRCUIT_BREAKER` with a resume time it will not keep. Companion updates: the trading page of the documentation site. |
| 3.0 | 2026-08-04 | Two additions that make the size contract total, neither reachable at any tradable size. (1) An ABSOLUTE per-order size ceiling on every market, on top of whichever per-order cap that market publishes (section 2a): one order's `price x qty` must stay within the venue's exact-money range, `1000000000000000000` settlement-currency units, or it rejects `2013`. It applies in both cap arms (`maxOrderNotional` where published, base-unit `maxOrderQty` otherwise), on every market kind, to amends on the full amended quantity, and to RFQ creation and quotes on the same effective-price selection the published caps use. It is not a risk parameter and is not published per market: it sits orders of magnitude above every `maxOrderNotional` in force, and it is the bound that lets the venue represent every trade, fee and position update exactly in integer money, since a fill always executes at the RESTING order's own price. (2) A new `cancelReason` value `POSITION_SIZE_LIMIT` (complete code reference section 5.1) for the same guarantee one level down: where settling a fill would take a position beyond that same range, the order is canceled with this reason instead of settling a position the venue cannot describe exactly. It is VENUE-INITIATED and reaches you on the order channel like any other unsolicited cancel; treat it as you already treat `SPOT_UNFUNDED`, and never retry the same order unchanged. (3) The same absolute ceiling applied to barrier contracts (section 6b): a quote on a barrier RFQ whose implied maximum loss, the entry-to-barrier distance times `qty` times the contract value, would exceed the exact-money range rejects `2027`, since that distance is exactly the holder margin and the knock-out settlement the venue must represent; the entry-priced size caps deliberately do not see it. All are stated so the published rules have no gap at the extremes; no current or foreseeable member order approaches any of them. `/openapi.json` `info.version` is now 2.7.0. Companion updates: complete code reference, FIX rules of engagement, REST integration manual and Integration Pack, architecture diagrams, documentation site. |
| 2.10 | 2026-08-04 | New error code `5005` COMMAND_FAILED, the only member-visible part of a venue resilience change. A command that the venue accepted and durably recorded but could not finish processing is now reported to you and rejected, instead of interrupting the venue: trading continues normally for you and for everyone else. On order entry `5005` behaves like every other engine rejection, arriving as `data.rejectCode` inside a `200 / code: 0` response, on the WebSocket `order` channel, and on FIX as `9010=5005`; on surfaces that carry a status of their own it is HTTP 500. What is different from other rejections: the rest of `data` reports what the command DID achieve before it stopped, so an `orderId` in that response is a real order that may be resting or partially filled and any `executions` listed really happened. Reconcile against `GET /v1/orders/open` and `GET /v1/positions` before doing anything else, and never resubmit the identical request: the venue is deterministic, so an unchanged resubmission fails identically. Report the `clOrdId` to venue operations. This code should be rare; treat a repeat on the same market as a reason to pause and call operations. No field, endpoint or payload shape changed, and `/openapi.json` `info.version` is now 2.6.3. Companion updates: complete code reference (revision 2.9), REST integration manual, Integration Pack, documentation site. |
| 2.9 | 2026-08-03 | FIX market data becomes a streaming feed (FIX rules of engagement section 5; no REST or WebSocket change, so `/openapi.json` `info.version` stays 2.6.0). Three additions to `MarketDataRequest(35=V)`, all optional and all defaulting to today's behavior. (1) `MDUpdateType(265)`: `0` or absent keeps the conflated `MarketDataSnapshotFullRefresh(35=W)` stream every current FIX client receives, `1` selects `MarketDataIncrementalRefresh(35=X)`, an initial `W` snapshot followed by one `X` per commit batch carrying only the changed levels as `MDUpdateAction(279)` `0` New / `1` Change / `2` Delete with `MDEntryType(269)` `0` bid or `1` offer, `MDEntryPx(270)`, the level's new AGGREGATE `MDEntrySize(271)` (`0` on a Delete) and an informational 1-based `MDEntryPositionNo(290)`; books are keyed by PRICE, never by position. (2) A public trade tape: `MDEntryType(269)=2` entries on `35=X` carry `MDEntryID(278)` (the same trade id `GET /v1/trades` returns), price, quantity, `MDEntryDate(272)`/`MDEntryTime(273)`, plus venue tags `9013` aggressor side and `9014` `NORMAL`/`LIQUIDATION`/`ADL`/`RFQ`. Within one `35=X` the trade entries come first, in engine order, then the book entries, which describe the state at the end of that batch. (3) `NoMDEntryTypes(267)`/`MDEntryType(269)` on the request filters the feed to bids, offers and/or trades; absent means all three, which is exactly what a `35=W` carries today. Guarantees: the subscription is registered and the snapshot built in one engine-thread step and released through the same post-durability gate as your ExecutionReports, so an increment can never overtake the snapshot it is diffed against and no market data frame is ever ahead of the durable state it describes; re-issuing the request replaces the subscription immediately and anything already built for the subscription you replaced is discarded rather than delivered after the new snapshot, so narrowing `MarketDepth(264)` or the `MDEntryType(269)` set cannot leave stale entries in your book; loss is detectable by `MsgSeqNum(34)`, which market data shares with ExecutionReports and which is strictly increasing for the life of a session on every path, including the GapFill `SequenceReset(4)` that answers a `ResendRequest(2)`; a session that falls behind is never silently gapped, the venue withholds increments and re-baselines it with a fresh complete `35=W` (treat any mid-stream `W` as "discard and rebuild"), while trade prints are best effort and reconcile against `GET /v1/trades`; recovery is simply to re-issue the `MarketDataRequest`. Limits: depth still capped at 25 levels a side, and a new cap of 32 instrument subscriptions per session (re-subscribing an instrument already held replaces it and is never refused). New `MarketDataRequestReject(35=Y)` reasons `MDReqRejReason(281)`: `2` subscription limit reached or engine momentarily busy, `6` unsupported `MDUpdateType`, `8` unsupported `MDEntryType`; a rejected request subscribes nothing. Companion updates: FIX rules of engagement (revision 2.2), the FIX integration manual, the FIX page of the documentation site. |
| 2.8 | 2026-08-03 | Notional order caps (section 2a), completing the notional-limits model revision 2.7 introduced for positions. New optional instrument field `maxOrderNotional` on `GET /v1/instruments`: a decimal settlement-currency string (USD on every current listing), absent or null on markets without a per-order notional cap. Where set, the per-order size cap on that market is denominated in NOTIONAL instead of base units and takes precedence over `maxOrderQty`, which becomes informational legacy; where absent, the base-unit `maxOrderQty` check applies unchanged. The 2013 check values each single message at its effective price: priced orders (including the limit leg of conditionals) at their limit price, market orders at the current mark, amends re-checked on the FULL amended quantity at the same price selection, RFQ requests at the current mark at creation and every quote at its quoted entry price for the request's full size. Deliberate asymmetry with the position cap: `reduceOnly` orders are NOT exempt (the cap is a fat-finger guard on a single message and applies to closes too, so closing a large position may take several orders); venue-initiated closes (liquidation, ADL) remain exempt so the venue can always close any position. The venue standard for perpetual listings is `maxOrderNotional` 10000000 USD beside the 2000000000 USD `maxPositionNotional`; the caps in force on each market are the ones published on `GET /v1/instruments`. Section 2a's limits block is rewritten notional-first for BOTH caps, and the code 2013 registry row now carries the valuation rules. `/openapi.json` `info.version` is now 2.6.0. Companion updates: complete code reference, Technical Handbook, REST integration manual and Integration Pack, documentation site. |
| 2.7 | 2026-08-03 | Notional position caps (section 2a). New optional instrument field `maxPositionNotional` on `GET /v1/instruments`: a decimal settlement-currency string (USD on every current listing), absent or null on markets without a notional cap. Where set, the position cap on that market is denominated in NOTIONAL instead of base units, giving every market uniform dollar capacity so smaller markets no longer reach their cap first in a correlated move. The 2011 check keeps its worst-case shape (your net position plus same-direction working orders plus the incoming order) and values that worst case at the CURRENT mark price; an order or amend that would push the value beyond the cap rejects with 2011, on the order book and at RFQ accept alike (your own breach 2011, the quoting member's 3007). Risk-reducing flow is never blocked by the cap, and a position that comes to exceed the cap only because the mark moved is never forced to reduce: the cap gates increases only. Markets without a notional cap keep the legacy base-unit `maxPositionQty` check unchanged; where both fields appear the notional cap takes precedence and `maxPositionQty` is informational legacy. `maxOrderQty` deliberately stays unit-denominated: it is a per-order fat-finger guard, not a risk capacity limit. The venue standard for perpetual listings is a `maxPositionNotional` of 2000000000 USD; the caps in force on each market are the ones published on `GET /v1/instruments`. `/openapi.json` `info.version` is now 2.5.0. Companion updates: complete code reference, Technical Handbook, REST integration manual and Integration Pack, documentation site. |
| 2.6 | 2026-08-03 | Batch order entry (new section 6c). `POST /v1/orders/batch` and the WebSocket op `orders.batch` submit 1 to 50 COMPLETE, INDEPENDENT orders in one signed message, each entry a full place-order body with its own `clOrdId` and its own `clientUid` (the mass-close use case: one message, N client-tagged `reduceOnly` closes). Non-atomic by design: each entry validates and executes independently through the identical checks as a single order (including `minNotional` 2028 and its reduce-only exemption, strict fields 2017, reserved `KX-` namespace 2020); entries reach the engine in array order and `data.results` returns in array order, a successful entry shaped exactly like the single-order place response data (engine rejects ride `rejectCode`) and a gateway-level failure entry shaped `{code, msg, clOrdId}`. The envelope stays `code` 0 / HTTP 200 for mixed and even all-reject batches; envelope-level errors are limited to authentication, throttle (1003), malformed body or entry count outside 1 to 50 (2002 with a descriptive msg) and the order-to-trade block (1004). Rate limiting: a batch consumes one order-budget unit per entry, charged up front all or nothing (a denied batch consumes nothing and the 1003 carries `retryAfterMs` for the full deficit), and every entry counts as an order message under the order-to-trade regime. Not offered over FIX: FIX members submit singles. Rate-limit wording clarified everywhere (section 4, portal, OpenAPI): order placements, batch entries, and amendments all consume the order budget; cancels are never throttled. `/openapi.json` `info.version` is now 2.4.0. Companion updates: REST and WebSocket integration manuals, Technical Handbook, FIX rules of engagement note, documentation site. |
| 2.5 | 2026-08-02 | Decimal base-unit quantities, market re-listing and minimum notional (section 2a rewritten). Ships together with a venue data reset announced by venue operations; nothing carries across it. Quantities on every surface (order and RFQ `qty`, amends, positions, book depth, executions, FIX tag 38) are now DECIMAL BASE-ASSET UNITS instead of integer contract counts; prices were per base unit before and stay per base unit, so they look unchanged. Old integer quantity strings remain valid. New instrument fields on `GET /v1/instruments`: `qtyStep` (the quantity grid; a `qty` that is not an exact multiple rejects 2002), `minQty` (equals `qtyStep` on every current listing) and `minNotional` (decimal string). New error code `2028` BELOW_MIN_NOTIONAL: an order or RFQ whose `price × qty` is below `minNotional` (5 USD on all current listings) rejects; priced orders valued at the limit price, market orders at the current mark, amends re-checked on the full amended quantity, RFQ creates valued at the mark and quotes at the quoted entry price. Exempt from the floor: venue-initiated closes (liquidation, ADL) and member `reduceOnly` orders within the reducible position, so a sub-floor residual position always stays closable by its holder. Re-listed markets (tick unchanged): BTC-USD-PERP qtyStep 0.0001, ETH-USD-PERP 0.001, SOL-USD-PERP 0.01, BNB-USD-PERP 0.001, XRP-USD-PERP 1, BTC-USD-KO 1. `maxOrderQty`/`maxPositionQty` keep their economic size and are denominated in base units. Companion updates: complete code reference, Technical Handbook, FIX rules of engagement, Postman collection, documentation site, and the new draft Contract Specifications for USD-settled perpetuals. |
| 2.4 | 2026-08-02 | Mark-price staleness ladder early warning (section 10.9). Two additive advisory types on the `venueEvents` channel, delivered on both sockets: `STALE_MARK_WARNING` `{symbol, tickAgeSec, reduceOnlyInSec}`, broadcast once per staleness episode when the age of a market's last mark price tick crosses half that market's staleness threshold, and `STALE_MARK_RECOVERED` `{symbol, tickAgeSec}` when the feed is fresh again. The ladder: warning at half the threshold, reduce-only via `STOP_TRADING` with reason `STALE_MARK_PRICE` at the threshold, automatic recovery via `RESUME_TRADING` with reason `MARK_PRICE_RECOVERED`. Advisory `eventId` namespace `advisory-{asOfSeq}-{n}`. Best-effort advisories: informational only, never in `SNAPSHOT`, never backfilled or replayed; a venue restart mid-episode may repeat a warning once. Brokers should treat the warning as the signal to widen quotes or pause quoting on that market. No other wire changes. Companion updates: WebSocket integration manual and the WebSocket page of the documentation site. |
| 2.3 | 2026-08-02 | Contract sizing and granularity documented (new section 2a): quantities are whole contract counts on every surface, fractional exposure comes from per-market contract sizing, spot delivers whole base units, barrier markets use the same mechanism. Signing guidance hardened (section 3): explicit warning that the venue verifies the query string exactly as transmitted and that some HTTP stacks (Python aiohttp/yarl) re-normalize percent-encoding after signing, plus a second worked test vector with a comma-bearing GET query. Wire alignment, all pre-production, bringing the implementation in line with the contract this document already stated: reusing a `clOrdId` with a DIFFERENT payload now rejects at the envelope level (HTTP 400, `code` 2009, `data.clOrdId` echoed; previously HTTP 200 with `code` 0 and only `data.rejectCode` set), and the WS `order.place` ack mirrors it (`success: false`, code 2009); rejected WS `order.place` acks now carry the registry wording in `msg`; untriggered conditional orders now report `PENDING_TRIGGER` (was `NEW`) on place acks, WS `order` frames and stream backfill rows, matching live reads, including `ORDER_AMENDED` while still untriggered; every event-derived order payload (place and cancel acks, WS `order` frames, terminal-order reads, stream backfill rows) now populates `type` and `tif` with the documented spellings (previously empty strings); WS `wallet` frames and stream `WALLET_ENTRY` rows now carry `balanceAfter` beside the equal-valued legacy `balance`, with a cross-surface field-name note in section 10.3. The `/openapi.json` `info.version` is now 2.2.0 and tracks this document's revision line going forward. Companion updates: position seeding and reconciliation guidance in the WebSocket integration manual; contract sizing in the Technical Handbook. |
| 2.2 | 2026-08-02 | Default-management hardening. While `riskStatus` is `LIQUIDATION` or `DEFAULTED` the venue rejects order-book placement and amends with `3002` on risk status alone, matching the behavior RFQ execution and withdrawals already had; previously the order-entry freeze rode only on `memberStatus` `SUSPENDED`. Cancels remain accepted throughout. A `DEFAULTED` member's status stays `SUSPENDED` until the venue clears the default. No wire, field or endpoint changes. |
| 2.1 | 2026-08-02 | RFQ markets (section 6b). `GET /v1/instruments` now carries `mode` (`ORDER_BOOK` \| `RFQ`): an RFQ market has no order book (order entry rejects with 2022; RFQ operations on an order-book market reject with 2023) and trades by request for quote in every kind: PERP (off-book perpetual trades, margined and netted exactly like book fills), SPOT (prefunded delivery at the quoted price) and the new BARRIER kind, bilateral knock-out contracts: the requester always holds (BUY long, barrier below the entry; SELL short, barrier above), the writer quotes the entry price, holder margin is the entry-to-barrier distance at entry, tracking the remaining distance thereafter (IM equals MM, the contract can never liquidate its holder) and the entry distance is also the maximum loss, termination AT the barrier price (guaranteed stop, no gap risk), writer margined on the risk tiers, no funding, settlement P&L as wallet kind `BARRIERSETTLE`, contracts on `GET /v1/barriers` and never in positions or open interest. REST surface: `POST /v1/rfq`, `GET /v1/rfq/open`, `GET /v1/rfq/mine`, `GET /v1/rfq/quotes/mine`, `POST /v1/rfq/{id}/quotes`, `POST /v1/rfq/{id}/accept`, cancel routes for RFQs and quotes (never rate-limited), `GET /v1/barriers`. Create/quote/accept consume the order-entry bucket and enforce `reg.traderId`. WS: new GUARANTEED private channels `rfq` and `barriers` with state-only recovery (sections 10.3, 10.7). RFQ trades publish flagged `RFQ` and never move the last-trade print, conditional orders or the circuit breaker. New codes 2022 to 2027 and 3007. REST/WS only; not offered over FIX. |
| 2.0 | 2026-08-01 | Kronos X v2.0 "Pacifica". Trader IDs and Authorised Users: `reg.traderId` on order entry, FIX tag `9012`, staged per-member enforcement, code `2021`. Member Portal: a venue-issued web login per member for positions, book, orders, end clients, reports and self-service credential rotation. Rate limits raised and split into two independent continuous-refill buckets (6000 reads/min, 40000 orders/min); a `1003` now carries the exact `retryAfterMs` rather than a whole window, and cancels consume neither bucket. Load shedding on the intake ring surfaces as `5001` instead of silent queueing. Liquidation now steps in 20% slices per the rulebook. Clustered deployments answer a write on a follower with `5030` plus the current leader. Snapshot-based recovery. |
| 1.2 | 2026-07-31 | Strict order entry (unknown fields rejected, code 2017). Optional `leverage`/`quoteCcy` order assertions (2018 on mismatch, member-aware validation), echoed on the ack. `clOrdId` echoed on gateway-level rejections; `clientUid` on every order lifecycle event including rejections. `KX-` reference namespace reserved (2020); venue-initiated closes carry `KX-LQ-{orderId}` / `KX-ADL-{tradeId}` with `refSource`. Error 3003 narrowed to halted; 3005 (no mark) and 3006 (delisted) added. New `venueEvents` channel (section 10.9). WebSocket delivery classes, gap frames and close codes 4008/4009 (section 10.7). Event stream recovery: `GET /v1/stream/events`, `/v1/stream/cursor`, `/v1/margin/calls` (section 11). `maxLeverage`/`leverageMode` on instruments; `imMultiplier` on accounts. History envelopes report the store watermark. |
| 1.1 | 2026-07-31 | Kronos X rename. Market Segments naming. Spot markets (section 6a). WS request/response parity (`get.*`). |
| 1.0 | 2026-07-30 | First member release. |

---

*Support: venue operations. Sandbox resets are announced in advance under the
section 1a policy; sandbox data carries no monetary value.*
