# Kronos X Conformance Test Plan

Revision 1.7, 20 October 2026, covering Kronos X v2.0 "Pacifica". Revision 1.7
tracks Member API revision 3.85: a signed write is accepted once (case A-10), the
receive window is refused rather than reduced and a timestamp may lead the venue
clock by at most 5 s (cases A-04 and A-11), the WebSocket login signs the
connection's challenge nonce (cases A-07, A-12 and A-13), and RFQ requests and
quotes are idempotent on their `clOrdId` from the venue's replay-contract line for
that release (case X-12); a member re-certifies these
items before it connects to a venue that runs the revision. Revision 1.6 was
20 October 2026, tracking Member API revision 3.83: case I-05 is reproducible on the clustered
sandbox by a leader hand-over that venue operations stage at an agreed time,
case I-04 names the single write a leader change can leave in flight, cases
B-01, B-04 and B-05 name what revision 3.83 states of the `REPAIRING` state
and of the cursor, case B-08 the condition under which the venue serves the
last-page position, and the prerequisites name the sandbox host; revision 1.5 was
19 October 2026, tracking Member API revisions 3.80 and 3.81: case X-04 expects the lots' figures
as section 6e states them since 3.80 (the quantity sums exactly to the bucket,
the unrealized PnL only until a partial close leaves lots at different entry
prices), and nothing else changes; revision 1.4 was 29 September 2026,
tracking Member API revision 3.74: the batch amendment and batch cancellation
routes (cases X-07 to X-11 and the batch evidence of section 5), a surface a
member that adopts them re-certifies under section 7; revision 1.3 was
28 September 2026, tracking Member API revision 3.73: the position cursor on
the last page of `GET /v1/stream/events` (case B-08) and the retry of a
backfill page refused with 5007 or 1003 (case B-09); revision 1.2 was
10 September 2026.

Operated by PM MTF Ltd. This document is the certification standard referenced by
the Member API reference section 1: production credentials are issued to a member
after conformance testing is passed on the sandbox.

**Document status:** Sandbox release for member integration testing. Companion to
the Member API reference (`KronosX-Member-API.md`), the FIX rules of engagement
(`KronosX-FIX-Gateway.md`) and the complete code reference
(`KronosX-API-Codes.md`). Where this plan and those documents disagree, they win
and this plan is corrected.

---

## 1. Purpose and what certification means

Conformance testing exists to establish, before a member trades real money, that
its system:

1. Handles every rejection the venue can return, rather than only the happy path.
2. Never double sends, and never abandons an order whose fate it did not observe.
3. Maintains an accurate view of its own orders, positions and cash.
4. Recovers correctly from a disconnect without operator intervention.

A pass certifies the MEMBER SYSTEM as tested, on the transports tested, at the
manual revision tested. It is not a warranty of venue behaviour and it does not
transfer any obligation from the member to the venue.

Admission to a swap market under the SWAP_COUNTERPARTY class of the Rules annex
requires the cases of Section W passed and reported under Rules section 6.

## 2. Scope: what you must test

Test only what you will use. Scope is the intersection of your ROLE and your
TRANSPORTS, agreed with venue operations before the session.

| Section | Broker (client flow) | Market maker (house flow) | Back office, drop copy only |
|---|---|---|---|
| A. Access and authentication | Required | Required | Required |
| S. Session and transport | Required | Required | Required |
| O. Order entry and validation | Required | Required | Not applicable |
| L. Order lifecycle | Required | Required | Not applicable |
| I. Idempotency and reply completeness | Required | Required | Not applicable |
| C. Client identification and reporting | Required | Required (house UID) | Not applicable |
| H. Hedge mode and reduce only | If used | If used | Not applicable |
| M. Market data | If consumed | Required | If consumed |
| R. Risk and margin | Required | Required | Not applicable |
| V. Venue state and staleness | Required | Required | Required |
| T. Throttling and resilience | Required | Required | Required |
| K. Safety controls | Recommended | Required | Not applicable |
| B. Backfill and reconciliation | Required | Required | Required |
| W. Swap markets | If used | If used | Not applicable |
| X. Optional surfaces | If used | If used | If used |

Transports are certified independently. A member that adds FIX after certifying
on REST recertifies section S and sections O, L and I on FIX only.

## 3. Prerequisites

Before booking a session, confirm all of the following:

- Sandbox member account issued, with a TRADE credential and, if your design uses
  one, a READ_ONLY credential.
- Test balances funded. Ask venue operations for a top up if you need a larger
  working balance for the risk tests.
- Your system points at the sandbox host, `sandbox.mtf.perpetuals.net` since
  Member API revision 3.83 (a clustered deployment that replaces
  `sandbox.mtf.perpetuals.com` from the date venue operations announce), never
  at production.
- You have read the Member API reference sections 3 (signing), 6 (order entry)
  and 10 (WebSocket), and, for FIX, the rules of engagement in full.
- You can produce an evidence bundle (section 5).

## 4. How the session runs

1. **Self service run.** You execute the plan against the sandbox at your own
   pace and collect evidence. Most members iterate here for several days.
2. **Witnessed run.** A venue engineer observes a live run of the mandatory items
   for your scope, over a scheduled call, typically 60 to 90 minutes. The venue
   reconciles what you report against its own durable record of the same
   commands.
3. **Result.** Pass, pass with observations, or retest. Observations are items
   that behaved correctly but revealed a fragility worth fixing; they do not
   block certification.

Retesting after a failure is scoped to the failed section, not the whole plan.

## 5. Evidence to submit

For each test item, submit enough for the venue to find the same events in its
own record:

- The `clOrdId` (or `MDReqID`, or cursor) you used, and the wall clock time.
- The venue response you received, verbatim, including the `code` and `asOfSeq`
  from the envelope, or the FIX message with its `ClOrdID(11)` and `ExecID(17)`.
- For market data items, the raw capture file.
- For a batch amendment or cancellation (revision 1.4, cases X-07 to X-11): each
  entry's `orderId` (and the `clOrdId` where the entry carried one) plus the
  response's `asOfSeq`, which both transports carry for these routes (the REST
  envelope, and the top level of the `orders.batchAmend` and
  `orders.batchCancel` acks).
- A one line statement of what your system DID as a result.

That last line is the point of the exercise. The venue can already see what it
sent you; conformance is about what your system concluded from it.

## 6. Pass criteria

An item passes when the observed venue response matches the expected result AND
your system's resulting state is correct. An item fails if your system:

- Retries a request whose outcome it did not observe, with a new reference.
- Treats an unrecognised code as success, or as a hard failure that stops trading
  when the code is retryable.
- Reports a position, balance or order state that disagrees with the venue after
  the item completes.
- Requires a human to restore correct operation after a disconnect.

---

## Section A. Access and authentication

| ID | Scenario | Expected |
|---|---|---|
| A-01 | Signed request with a valid credential | `code` 0, envelope carries `asOfSeq` |
| A-02 | Signature computed over the wrong body or path | 1001 AUTH_FAILED, 401 |
| A-03 | Timestamp older than the recv window (default 5000 ms) | 1002 TIMESTAMP_OUT_OF_WINDOW, 401 |
| A-04 | `X-RECV-WINDOW` above the venue ceiling of 60000 ms | 1002 with the sentence naming `X-RECV-WINDOW`, whatever the timestamp: since revision 1.7 (Member API 3.85) the value is refused, never reduced to the ceiling, and so is a value that is not an integer from 1 to 60000; your client sends at most 60000 or no header |
| A-05 | READ_ONLY credential attempts order entry, on every transport you use | On REST, 1005 KEY_NOT_PERMITTED with HTTP 403. On WebSocket, an ack carrying 1005 (no HTTP status). On FIX the credential is forced to drop copy at Logon, so `35=D`, `35=F` and `35=G` are refused at the SESSION layer with a `Reject(35=3)` naming the message type, not with 1005 |
| A-06 | Query string with a comma or other reserved character, signed exactly as transmitted | `code` 0. Confirms your HTTP layer does not re-encode after signing |
| A-07 | WebSocket `auth` op signing the connection's challenge nonce (the first frame of `/ws/private`, Member API section 10.1, revision 1.7) with `expiresMs` inside now to now plus 60 s | success true |
| A-08 | WebSocket `auth` op with `expiresMs` far in the future | auth refused. Far future tokens are rejected by design |
| A-09 | Multi key: issue a second TRADE key, authenticate on both, revoke one, confirm the other still works | Both authenticate; the revoked key returns 1001 immediately, including on live sessions |
| A-10 | Send a signed write (an order with a `clOrdId`), then the IDENTICAL bytes again (revision 1.7) | The second answers 1002 `request already received` and nothing runs; your retry path signs every attempt afresh with the same `clOrdId`, and that attempt answers `duplicate` true |
| A-11 | A timestamp more than 5000 ms ahead of the venue clock, with `X-RECV-WINDOW` 60000 (revision 1.7) | 1002 `timestamp ahead of the venue clock ...`; your clock is synchronised with the venue's |
| A-12 | WebSocket `auth` op with three args, the signature over `"GET/ws/private" + expiresMs` alone (revision 1.7) | 1001 `auth failed: sign the nonce of this connection's challenge frame ...`; the connection stays unauthenticated |
| A-13 | Disconnect and reconnect the private socket; send the previous connection's `auth` frame on the new one, then a login over the new challenge (revision 1.7) | The old frame answers 1001 `auth failed: the nonce is not this connection's challenge`; the new login succeeds. Your client signs each connection's own challenge and never logs the frame |

## Section S. Session and transport

| ID | Scenario | Expected |
|---|---|---|
| S-01 | REST over TLS to the published host | Connects; certificate validates against a public root |
| S-02 | WebSocket public and private sockets connect and receive a heartbeat | Connection stable; your client answers or tolerates heartbeats |
| S-03 | WebSocket idle with no inbound frames past the idle timeout | Venue closes with code 4009. Your client reconnects and resubscribes without operator action |
| S-04 | WebSocket slow consumer, stop reading under load | Venue closes with code 4008 after a terminal gap frame. Your client treats the gap as authoritative and resyncs |
| S-05 | FIX Logon with `98=0`, `141=Y`, `108` between 5 and 60, valid `553`/`554` | Logon echo |
| S-06 | FIX Logon with bad credentials | Logout mentioning 1001 |
| S-07 | FIX Logon omitting `141=Y` | Logout stating the requirement |
| S-08 | FIX TestRequest | Heartbeat carrying your `TestReqID(112)` |
| S-09 | FIX connection opened and left without a Logon | Venue closes it after the logon deadline of 10 s |
| S-10 | FIX drop copy: Logon with `9003=Y`, then attempt `35=D` | Logon echoes `9003=Y`; order entry refused at session level |
| S-11 | Disconnect mid session, reconnect, reconcile | Your open orders and positions after reconnect match the venue exactly. Use section B |

## Section O. Order entry and validation

Every item is a REJECTION your system must handle without stopping.

| ID | Scenario | Expected |
|---|---|---|
| O-01 | LIMIT GTC far from market | Accepted, rests |
| O-02 | Unknown symbol | 2001 UNKNOWN_SYMBOL |
| O-03 | Price off the tick grid, or quantity off the qty step | 2002 TICK_LOT_MISALIGNED. The venue never rounds |
| O-04 | Price outside the instrument price band | 2003 PRICE_OUTSIDE_BAND |
| O-05 | Trailing stop, or any order type not published | 2004 UNSUPPORTED_ORDER_TYPE. The venue has no trailing stops and never will |
| O-06 | PostOnly that would cross | 2005 POST_ONLY_WOULD_CROSS |
| O-07 | FOK that cannot be filled in full | Status CANCELED with `cancelReason` FOK_UNFILLABLE. Current builds CANCEL an unfillable FOK, they do not reject it: do NOT assert `rejectCode` 2006, which is retained in the registry for historical captures only |
| O-08 | MARKET order with nothing resting on the other side | Status REJECTED with 2007 NO_ELIGIBLE_LIQUIDITY |
| O-09 | Notional below the instrument `minNotional` | 2028 BELOW_MIN_NOTIONAL |
| O-10 | Order above the per order notional cap | 2013 INVALID_ORDER |
| O-11 | Projected position above the position cap | 2011 POSITION_LIMIT |
| O-12 | Unrecognised JSON field on the order body | 2017 UNKNOWN_FIELD. The venue never silently discards an assertion |
| O-13 | `clOrdId` beginning `KX-` | 2020 RESERVED_CLORDID |
| O-14 | Broker member omits `clientUid` | 2010 CLIENTUID_REQUIRED |
| O-15 | Order on an instrument with no sequenced mark | 3005 NO_MARK_PRICE |
| O-16 | Order while the instrument is halted | 3003 INSTRUMENT_HALTED |
| O-17 | Order exceeding available margin | 3001 INSUFFICIENT_MARGIN |
| O-18 | Batch of 1 to 50 entries, including at least one entry that rejects | Results return in array order; the batch is non atomic, so accepted entries stand |
| O-19 | Batch above 50 entries | Rejected |
| O-20 | Conditional order (stop or take profit) accepted but untriggered | Status PENDING_TRIGGER; no queue position fields present |
| O-21 | GTD order with an expiry in the past | Rejected |
| O-22 | Queue position on a resting order | Ack carries `queueQtyAhead` and `queueOrdersAhead`; first order at an empty level reports `"0"` and `0`. On FIX, tags 9017 and 9018 on the accept report |
| O-23 | Queue position on an order that did NOT rest (full fill, IOC remainder, rejection) | Fields OMITTED, not null. Presence is the signal that the order rested |

## Section L. Order lifecycle

| ID | Scenario | Expected |
|---|---|---|
| L-01 | Cancel a resting order | Canceled, reason USER_REQUESTED |
| L-02 | Cancel an order id that does not exist | 2014 ORDER_NOT_FOUND, 404 |
| L-03 | Cancel an order already terminal | 2014 |
| L-04 | Amend quantity down | Accepted, queue priority retained |
| L-05 | Amend price, or quantity up | Accepted, queue priority lost (amend as new) |
| L-06 | Mass cancel scoped to a symbol, and unscoped | Count returned matches the orders that were open |
| L-07 | Partial fill, then cancel the remainder | Your position reflects the filled part exactly; the cancel reports the correct remaining quantity |
| L-08 | Order fully filled | Terminal state observed once. On FIX, exactly one report with a lifecycle `ExecType(150)` carries a terminal `OrdStatus(39)`; a restatement `150=I` never closes an order |

## Section I. Idempotency and reply completeness

This section is where most integrations fail. Treat it as mandatory.

| ID | Scenario | Expected |
|---|---|---|
| I-01 | Resend a request with the SAME `clOrdId` and byte identical payload | The ORIGINAL order state returns with `duplicate` true. No second order is created. On FIX, `150=I` restatement carrying `9015=Y` |
| I-02 | Reuse a `clOrdId` with a DIFFERENT payload | 2009 DUPLICATE_CLORDID, 400 |
| I-03 | Request that returns 5005 COMMAND_FAILED | The rest of the response reports what the command DID achieve. An `orderId` present is a REAL order that may be resting or filled. Your system reconciles against it and does NOT resubmit |
| I-04 | Request that returns 5001 ENGINE_BUSY | Your system retries with the same `clOrdId`, never with a new reference. Outside a leader change the command was not accepted. On a clustered deployment a SINGLE write answered 5001 while its node lost the leadership may already be in the venue's log and may still execute (Member API section 10.7, revision 3.83): your system reconciles it by `clOrdId` on the leader before resubmitting, or resubmits it with the same `clOrdId`. A batch row answered 5001 was not accepted (Member API section 6c) |
| I-05 | Handling of 5030 NOT_LEADER, at a leader hand-over venue operations stage on the clustered sandbox at an agreed time (revision 1.6) | Your system follows the leader address returned only when it equals the entry host it is configured with, or retries that entry host, on a new connection, and never sends credentials to another host. It does not treat 5030 as a rejection of the order: nothing feeds a reject count, a kill switch or an instrument disablement, and trading continues on the leader without a human. A cancel, cancel-all, amendment or cancellation batch answered 5030 did not take effect and is sent again on the leader at once; a `session.cancelOnDisconnect` answered 5030 is not armed, and every arming is renewed after the reconnect. What the hand-over shows depends on the transport (Member API section 10.7): on REST a write on a connection kept alive to the node that led answers 5030 there, and that node then closes the connection; on the WebSocket the node that led closes your private sockets with 1001 `leader change, reconnect`, and 5030 appears only on a socket opened on a node that is not the leader, which your system closes itself at the first 5030; on FIX the session receives `Logout(35=5)` with `Text(58)` `leader change, reconnect` and reconciles by `ClOrdID(11)`. Until the clustered sandbox is in service, certify this item by code inspection or an offline assertion against a canned payload, and mark it as such on the sign off; before revision 1.6 that was the only way, because the sandbox ran single node |
| I-06 | Timeout with no response at all | Your system resolves the outcome by reading order state, NOT by resending with a new reference |

Coordinate I-05 with venue operations so the leader hand-over happens at a known
time, as for R-06 and R-07. During a leader change a write can also answer 5001
(I-04), 5000 (on REST, and since Member API revision 3.83 on the WebSocket
too, when its commit was not confirmed within 10 seconds) or a batch row 5000,
or get no ack after the 1001 close: none of these proves the order was not
accepted, so your system resolves them as in I-06, never as a 5030.

## Section C. Client identification and reporting

| ID | Scenario | Expected |
|---|---|---|
| C-01 | Order carrying `clientUid` | Accepted; the identifier is echoed on acks, executions and, on FIX, `Account(1)` |
| C-02 | Submit an identity record for a natural person: country, `YYYYMMDD` date of birth, first name, surname | Stored; response carries the derived identifier and a monotonic `revision` |
| C-03 | Submit an identity record for a legal entity with an LEI | Stored; LEI checksum validated |
| C-04 | Submit an identity record with a malformed field | 2030 CLIENT_IDENTITY_INVALID; the message names the offending field |
| C-05 | Read an identity record that does not exist | 2031 CLIENT_IDENTITY_NOT_FOUND, 404 |
| C-06 | Trade under a `clientUid` with no identity on file, then read the missing list | The trade is ACCEPTED (trading is never blocked on identity data) and the `clientUid` appears on the missing list. Your process supplies it within the T plus 1 window |
| C-07 | Authorised Users: submit an order with a trader id once your firm has registered users | Accepted. Omitting it once users are registered returns 2021 TRADER_ID_REQUIRED |
| C-08 | On FIX, carry the trader id and any regulatory fields you use on `35=D` | The stored regulatory record is identical to the same order sent over REST |

Do not construct the regulatory identifier yourself. Submit the component fields
and the venue derives it to the applicable standard.

## Section H. Hedge mode and reduce only

Only if you use these features.

| ID | Scenario | Expected |
|---|---|---|
| H-01 | Order without `positionSide` | Acts on the netting sub position, exactly as before the field existed |
| H-02 | Orders tagged LONG and SHORT for one `clientUid` on one instrument | Two independent sub positions coexist. On FIX, tag 9016 |
| H-03 | BUY tagged SHORT against an open short bucket | Reduces the short bucket, does not open a long |
| H-04 | `positionSide` on a spot instrument | 2016 SPOT_UNSUPPORTED |
| H-05 | `positionSide` with a value other than LONG or SHORT | 2013 INVALID_ORDER |
| H-06 | `reduceOnly` order exceeding the reducible quantity | 2008 REDUCE_ONLY_VIOLATION. Note the reduce only budget is MEMBER level |
| H-07 | Confirm member level margin is unchanged by hedged client flow | Member position stays the net sum over buckets |

## Section M. Market data

| ID | Scenario | Expected |
|---|---|---|
| M-01 | Subscribe `orderbook.{depth}.{symbol}` at a published depth (1, 5, 25 or 100) | Snapshot then deltas, with `prevSeq` chaining |
| M-02 | Subscribe at an unpublished depth token | Refused with 2013. The venue never clamps silently |
| M-03 | Verify the CRC32-C checksum on every delta at depth 25 or 100 | Every update verifies. Deltas carry window repair rows after deletes, so a depth limited window never goes short |
| M-04 | Force a `prevSeq` mismatch (drop a frame deliberately) | Your client resyncs rather than continuing on a drifted book |
| M-05 | Reproduce the REST orderbook checksum byte for byte from your maintained book | Identical |
| M-06 | Exceed the per session subscription cap | Refused cleanly. The cap differs by transport: 64 channels per WebSocket session (refused with 2013), 32 market data subscriptions per FIX session |
| M-07 | `matchableBook` subscription | Rows carry three elements. The SNAPSHOT carries a `checksum`, computed over price and matchable quantity; only the DELTAS omit it. Recover by resync rather than by verifying deltas |
| M-08 | FIX market data `35=V` with `263=1` and `265=1`, then `35=X` increments | New, Change and Delete entries applied correctly; a level rising into the window arrives as New. Note `263` selects the subscription (`263=1` is snapshot then updates), while `265` only chooses the update SHAPE: `263=0` sends one `35=W` and nothing further even with `265=1`, and omitting `263` is refused with `281=4` |
| M-09 | Trade tape | Every print observed once, with the venue trade id |
| M-10 | Mark price, index and funding rate channels | Consumed without assuming they update on a fixed cadence |

## Section R. Risk and margin

| ID | Scenario | Expected |
|---|---|---|
| R-01 | Margin preview for a basket | Figures describe the WHOLE basket; `accepted` answers the admission gates |
| R-02 | Preview a basket that over closes with reduce only entries | Reported rejected, matching what sending it would do |
| R-03 | Open a position, verify equity, available, initial and maintenance margin against your own calculation | Agreement to the micro |
| R-04 | Funding settlement while holding a position | Funding lands in your balance and on the funding statement. After an outage, one settlement per elapsed anchor |
| R-05 | Read your positions and end client sub positions | One row per bucket; totals reconcile to the member position |
| R-06 | Drive a test account to a margin call | Margin call event observed and acted on by your system |
| R-07 | Observe a liquidation on a test account | Your system tracks the forced orders and the resulting position and cash correctly. Venue generated references carry the `KX-` prefix and `refSource` VENUE |

Coordinate R-06 and R-07 with venue operations so they happen at a known time.

## Section V. Venue state and staleness

| ID | Scenario | Expected |
|---|---|---|
| V-01 | Subscribe to venue events on both sockets | Typed frames received; duplicates across sockets deduplicated by event id |
| V-02 | Instrument halted by the venue | Your system stops sending on that instrument and does not treat the rejections as a system fault |
| V-03 | Reduce only session, and post only session | Your system honours the restriction rather than discovering it by rejection |
| V-04 | Stale mark ladder: warning, then reduce only, then recovery | Each transition observed and acted on. Note the sandbox stale timeout is 60 s against 300 s in production, deliberately, so the ladder is quick to exercise |
| V-05 | Venue halted | 5002 VENUE_HALTED handled as a temporary state |

## Section T. Throttling and resilience

| ID | Scenario | Expected |
|---|---|---|
| T-01 | Exceed the read budget | 1003 RATE_LIMITED with the exact wait in `retryAfterMs`. Your client sleeps that long, it does not assume a fixed window |
| T-02 | Exceed the order budget | 1003. Cancels are never throttled, verify you can still cancel |
| T-03 | Confirm the budgets are per MEMBER | Two credentials of the same member draw the same buckets. Extra keys do not multiply the budget |
| T-04 | Breach the order to trade ratio threshold. PRECONDITION: OTR thresholds are per member and UNSET by default, so ask venue operations (members Slack channel) to configure a block threshold for your test member first; without one, no volume of untraded orders produces a 1004 | 1004 OTR_BLOCKED. New orders and amendments blocked, cancels still pass |
| T-05 | Sustained burst at your intended production rate | No unhandled rejection class, no unbounded local queue |

## Section K. Safety controls

| ID | Scenario | Expected |
|---|---|---|
| K-01 | Arm cancel on disconnect, then drop the session | After the grace window, with no live TRADING session left on that transport, every resting order of the member is canceled with reason USER_REQUESTED. A live DROP COPY session does not count as the member being back and does not suppress the cancel |
| K-02 | Arm with a grace window, drop, reconnect and authenticate INSIDE the window | The pending cancel is disarmed; orders survive |
| K-03 | Arm on WebSocket via `session.cancelOnDisconnect`, and on FIX via Logon tag `9004=Y` with the grace in `9005` | Both accepted; grace clamped to 0 to 30000 ms; FIX echoes `9004` on the Logon reply |
| K-04 | Attempt to arm with a READ_ONLY credential | On WebSocket, refused with 1005. On FIX there is no refusal: `9004=Y` from a drop copy credential is ignored and the Logon reply echoes `9004=N`, so assert the echo rather than an error |
| K-05 | Portal kill switch | Cancels every working order; your system observes the cancels and does not resubmit automatically |

Cancel on disconnect cancels ORDERS ONLY. The venue never closes, reduces or
hedges a position on disconnect, deliberately.

## Section B. Backfill and reconciliation

| ID | Scenario | Expected |
|---|---|---|
| B-01 | Read the stream cursor | Store status is COMPLETE, CATCHING_UP or REPAIRING; your system handles all three. REPAIRING follows every leader change on the clustered sandbox, describes the node that answered, and has no maximum duration (Member API section 11.3, revision 3.83) |
| B-02 | Disconnect, miss events, backfill from your resume point (Member API section 11.6) | Every missed event recovered in order, with no gap and no duplicate applied twice |
| B-03 | Backfill during CATCHING_UP | Pages are never partial and never contain holes; your client keeps polling from `nextCursor` |
| B-04 | Backfill during REPAIRING | Your client keeps polling from `nextCursor`, which stops below the hole and never passes it, and raises an operational alert; meanwhile it recovers the state it needs from the state reads (Member API section 10.8 step 7), keeps consuming live frames, and sizes no alert to a maximum duration, which the venue does not set |
| B-05 | Malformed cursor, or an unknown channel family | 2019 INVALID_CURSOR handled. A well-formed 30-digit cursor whose `seq` or `eventIndex` field lies above its range is 2019 too, and since Member API revision 3.83 its `msg` names the field and its range (Member API section 11.2): branch on the code, never on the `msg`. Note a cursor that is merely OLDER than the retention horizon is well formed and does NOT return 2019: it returns a normal page that is silently short, which is the condition B-07 covers |
| B-06 | End of day reconciliation of positions, trades and cash against the venue statements | Full agreement |
| B-07 | Backfill from a cursor older than the retention horizon of 35 days | The page returns normally and is SHORT, with no error. Your client detects the condition from the retention fields on the cursor response, not from an error code, and falls back to snapshot reads plus the dated history routes |
| B-08 | From revision 3.73: a filtered poll that matched nothing | Where the venue serves the last-page position (on an environment from the date venue operations announce for it there, Member API section 11.3; revision 3.83 states the condition): nextCursor sits at the watermark and the next poll from it returns only newer events and never a gap. Before that date nextCursor is the cursor you sent. Under both, the paging loop stops on hasMore false or count 0, never on nextCursor equal to the cursor sent |
| B-09 | A backfill page answers 5007 or 1003 | The client sleeps retryAfterMs, retries the same page with the same cursor, never advances its cursor on the refusal, and narrows the read after repeated 5007 |

## Section W. Swap markets (writer-first offers and takes)

Only if you use the swap markets. Passing and reporting these cases under Rules
section 6 is criterion (3) of the SWAP_COUNTERPARTY class (Member API reference
section 6h): admission to a swap market is granted per market and covers both
roles. The cases need two admitted credentials on the sandbox (a writer and a
taker); venue operations admit the certification credentials for the session.

| ID | Scenario | Expected |
|---|---|---|
| W-01 | Read the six swap rows on `GET /v1/instruments` | `kind` SWAP, `mode` RFQ, `accessMode`, `swapMinTenorDays`, `swapMaxTenorDays`, `swapMaxRateBps`, `swapOfferValiditySec` present |
| W-02 | Before admission, `POST /v1/rfq` offer | 3009 MARKET_ACCESS; your access on `GET /v1/account/parameters markets[].access` reads the market as not ALLOWED |
| W-03 | After admission, post an offer within the tenor and rate bounds | Visible on `GET /v1/rfq/mine` and, from a second admitted credential, on `GET /v1/rfq/open` |
| W-04 | Take a size at or above the offer's minimum from the second credential | An execution on `GET /v1/executions` and the `execution` frame with `flags` `SWAP` (one value; the venue ranks SWAP above RFQ, so no RFQ flag appears) and `price` = rateBps as a whole number, the remaining lots on the offer, a contract on `GET /v1/swaps` for both parties |
| W-05 | The term rejects | 2036 SWAP_TERMS on a tenor or rate outside the bounds, 2037 SWAP_OFFER_ONLY on a quote, 2038 SWAP_SIZE on a size below the offer's minimum |
| W-06 | Staged by venue operations (the writer's asset balance withdrawn before a maturity) | 3007 on a take whose writer became unfit (offer withdrawn, `rfq` canceled frame to the writer) and 3008 on the writer's next offer while the claim is outstanding |
| W-07 | Settlement at maturity, or a delisting | SWAPSETTLE ledger rows (principal plus reward) on the ledger feed, or the pro rata final settlement |
| W-08 | Disconnect and reconnect | The `swaps` and `rfq` WS channels resubscribed and the contract set reconciled against `GET /v1/swaps` |

## Section X. Optional surfaces

Only if you use them.

| ID | Scenario | Expected |
|---|---|---|
| X-01 | RFQ: create a request, receive quotes, accept one | Trade printed; quoting your own request returns 2026 RFQ_SELF_TRADE |
| X-12 | RFQ: resend a request, and a quote, with the `clOrdId` already used (revision 1.7, Member API 3.85; on an environment past the venue's replay-contract line for that release) | 2009 DUPLICATE_CLORDID, nothing opened or replaced; your system finds the first send on `GET /v1/rfq/mine` or `GET /v1/rfq/quotes/mine` and re-quotes under a NEW `clOrdId` |
| X-02 | RFQ on a book market, or a book order on an RFQ market | 2023 NOT_RFQ_MARKET, or 2022 RFQ_MARKET_ONLY |
| X-03 | Barrier knock out contract via RFQ | Contract opens; knock out settles at the barrier |
| X-04 | Lots: read open lots for a client | Each opening fill listed with entry and unrealized PnL; the lots' qty sums to the bucket's net quantity; their unrealized PnL sums to the position's only until a partial close leaves lots at different entry prices (Member API section 6e; before revision 1.5 this row expected the unrealized sum to match always) |
| X-05 | Close one lot by id | That lot is the one consumed. An unknown lot id returns 2032 UNKNOWN_LOT, 404 |
| X-06 | Self trade prevention with more than one `clientUid` | Under the default end client scope, different identifiers may cross and both sides pay fees; the same identifier triggers your configured policy, 2015 under REJECT_TAKER. Member level scope is available on request |
| X-07 | Batch amendment (`POST /v1/orders/batch/amend` or `orders.batchAmend`, revision 1.4): one batch with a size down, a reprice, an unknown `orderId` and an off grid price | HTTP 200, envelope `code` 0, one row per entry in array order; the size down keeps its priority (the same `acceptedTs`), the reprice stands at the new price, the unknown order is a 2014 row and the off grid price a 2002 row; the other entries stand, and your system reads the rows by the rule "a row carrying `code` is a failure row" |
| X-08 | Batch cancellation (`POST /v1/orders/batch/cancel` or `orders.batchCancel`) by `orderId` and by `clOrdId` while your order budget is spent | Every entry cancelled (`status` `CANCELED`, `cancelReason` `USER_REQUESTED`), no 1003: a cancellation batch is never throttled |
| X-09 | An order named twice in one batch | The first entry runs; the later entry answers a 2013 row naming the earlier entry, and nothing is submitted for it |
| X-10 | A full ladder requote: 24 levels in one amendment batch, ordered the side moving away first and sizes down before sizes up | Every row reconciled against `GET /v1/orders/open` at or after the response's `asOfSeq`; your system never resends a batch whole blind and reads back before acting on a refused entry |
| X-11 | A post only ladder repriced into the book | The crossing entries read `REJECTED` with `rejectCode` 2005 inside success rows (that level is gone); your system re-places those levels with a placement batch |

---

## 7. Re-certification

Re-certify when any of the following happens:

- You add a transport, or a surface outside your certified scope (for example
  the batch amendment or cancellation routes of Member API revision 3.74:
  re-certify with cases X-07 to X-11 before you use them on production).
- The venue ships a BREAKING manual revision. Additive revisions do not require
  re-certification; the changelog states which is which.
- You change the system materially, for example a new order router or a new
  reconciliation process.
- Twelve months elapse since the last pass.

## 8. Sign off

| Field | Value |
|---|---|
| Member | |
| Member code | |
| Role certified | Broker, market maker, or back office |
| Transports certified | REST, WebSocket, FIX |
| Optional sections in scope | |
| Manual revision tested | |
| Sandbox run completed | Date |
| Witnessed run completed | Date |
| Result | Pass, pass with observations, retest |
| Observations | |
| Venue signatory | |
| Member signatory | |
