FIX connectivity
The same accounts, guards and error codes as the Trading API, over FIX 4.4 for systems that already speak it. This guide takes a FIX engine from first logon to a certified order flow. Every tag and value here comes from the FIX reference; nothing is added.
#Overview
api.your-brand.com:5201, TLS 1.2+qk_…) with scope fix57=QUOTE market data · 57=TRADE ordersYour broker's API access panel shows the exact host, the port, the TargetCompID (written OCTOLIT in this guide) and the id of your key. Messages in this guide are written with | for the SOH delimiter (0x01) and … where a value is yours.
#Two sessions
One TCP connection carries one session type. Open two connections for a full setup: a QUOTE session that only streams prices, and a TRADE session that only carries orders and reports. The CompIDs are the same on both; TargetSubID (57) chooses the type.
| Tag | Value |
|---|---|
49 SenderCompID | K<key id> - the numeric id of your Trading API key, e.g. K12 |
56 TargetCompID | The CompID from the panel; this guide writes it as OCTOLIT |
57 TargetSubID | QUOTE or TRADE |
554 Password | The API key itself. Scope fix is required; the key's IP allow-list and expiry apply exactly as for REST. |
141 ResetSeqNumFlag | Y. Sessions are not resumed across reconnects - start both sides at 1 every time. |
108 HeartBtInt | 5 - 120 seconds; 30 is a good default. |
141=Y, re-send your market data subscriptions, and send 35=AF (OrderMassStatusRequest) and 35=AN (RequestForPositions) to rebuild your state from the platform's answers. #Logon
-
Open TLS to the endpoint
The endpoint speaks TLS only (1.2 or newer). Verify the certificate chain as you would for HTTPS; the host name is the API host of your brand.
-
Send 35=A within a few seconds
Logon - TRADE session8=FIX.4.4 | 35=A | 49=K12 | 56=OCTOLIT | 57=TRADE | 34=1 | 98=0 | 108=30 | 141=Y | 554=qk_... |Logon - QUOTE session8=FIX.4.4 | 35=A | 49=K12 | 56=OCTOLIT | 57=QUOTE | 34=1 | 98=0 | 108=30 | 141=Y | 554=qk_... |98=0(no encryption inside the FIX layer - TLS does that),108your heartbeat,141=Y,554the key. -
Read the answer
Accepted: a
35=Aback with the same108. Refused:35=5Logout with the reason in58- one of the Trading API error codes, then the connection closes.Logout - refused logon8=FIX.4.4 | 35=5 | 49=OCTOLIT | 50=TRADE | 56=K12 | 34=2 | 58=invalid_api_key |invalid_api_key- the key is wrong, revoked or expired, or lacks thefixscope.ip_not_allowed- your address is not on the key's allow-list.open_api_disabled- API access is switched off for the brand or the client.
#Heartbeats and test requests
Test Request and Heartbeat work as FIX 4.4 defines them. Send 35=0 every 108 seconds when nothing else was sent; answer a 35=1 TestRequest with a Heartbeat that echoes 112. The platform sends its own TestRequest when your side goes quiet, and a session that stops answering is closed.
8=FIX.4.4 | 35=0 | 49=K12 | 56=OCTOLIT | 57=TRADE | 34=7 |8=FIX.4.4 | 35=1 | 49=OCTOLIT | 50=TRADE | 56=K12 | 34=9 | 112=TEST-1 |8=FIX.4.4 | 35=0 | 49=K12 | 56=OCTOLIT | 57=TRADE | 34=8 | 112=TEST-1 |Keep 108 short enough that a dead connection is noticed before your risk limits mind, and long enough that a slow link does not log you out - 30 seconds suits most.
#Order lifecycle
One NewOrderSingle, one or more ExecutionReports: 150=0 New first, then 150=F on a fill, 150=4 after a cancel, 150=5 after a replace, or 150=8 with the reason in 103 and the REST error code in 58.
-
NewOrderSingle (35=D)
The minimum is
11ClOrdID,55symbol (X:EURUSD, the numeric id, orEUR-USD; or48+22=8),54side,38quantity in units (lots × contract size),40type and59=1.1is the trading account id - optional when the key's client has one account. Stop loss and take profit ride along as absolute prices in9101/9102;9103picks the leverage when the symbol offers a choice.Market buy 10 000 EURUSD with SL and TP8=FIX.4.4 | 35=D | 49=K12 | 56=OCTOLIT | 57=TRADE | 34=2 | 11=ord-20261111-001 | 1=55 | 55=X:EURUSD | 54=1 | 38=10000 | 40=1 | 59=1 | 9101=1.0700 | 9102=1.0900 | 60=20261111-09:30:00.000 |11 ClOrdIDis theclient_order_idof the REST API. The same value is idempotent across FIX and REST: a duplicate answers150=8 103=6instead of a second order. -
ExecutionReport 150=0 - New
The platform accepted the order and assigned
37OrderID. For a market order this report is followed at once by the fill; for a limit or stop order it is the resting state.ExecutionReport - New8=FIX.4.4 | 35=8 | 49=OCTOLIT | 50=TRADE | 56=K12 | 37=812 | 11=my-order-1 | 17=... | 150=0 | 39=0 | 1=55 | 55=X:EURUSD | 54=1 | 40=2 | 44=1.0750 | 38=10000 | 14=0 | 151=10000 | 6=0 | 721=812 | 9101=1.07 | -
ExecutionReport 150=F - Trade
39=2Filled,31the price,32the quantity,14cumulative,151leaves 0.721is the position id - keep it, you need it to close or to match35=APposition reports.ExecutionReport - Filled8=FIX.4.4 | 35=8 | 49=OCTOLIT | 50=TRADE | 56=K12 | 37=812 | 11=ord-20261111-001 | 17=... | 150=F | 39=2 | 1=55 | 55=X:EURUSD | 54=1 | 38=10000 | 14=10000 | 151=0 | 31=1.0752 | 32=10000 | 6=1.0752 | 721=812 | -
ExecutionReport 150=8 - Rejected
39=8, the FIX reason in103and the REST error code in58. One error table serves both protocols, so your REST error handling applies unchanged.ExecutionReport - Rejected (market closed)8=FIX.4.4 | 35=8 | 49=OCTOLIT | 50=TRADE | 56=K12 | 37=0 | 11=ord-20261111-002 | 17=... | 150=8 | 39=8 | 103=2 | 58=market_closed | 55=X:EURUSD | 54=1 | 38=10000 | -
Close a position
A close is a NewOrderSingle with
721set to the position id and54the opposite side.38may be the whole position or less where partial closes are allowed. The answer is150=0then150=FwithOrderID C<closed row id>and721the position.Close position 8128=FIX.4.4 | 35=D | 49=K12 | 56=OCTOLIT | 57=TRADE | 34=5 | 11=close-812 | 721=812 | 55=X:EURUSD | 54=2 | 38=10000 | 40=1 | 59=1 | 60=20261111-10:00:00.000 |
#Pending orders: replace and cancel
8=FIX.4.4 | 35=D | 49=K12 | 56=OCTOLIT | 57=TRADE | 34=2 | 11=my-order-1 | 1=55 | 55=X:EURUSD | 54=1 | 38=10000 | 40=2 | 44=1.0750 | 59=1 | 9101=1.0700 | 60=20261010-12:00:00.000 || You send | Tags | Answer |
|---|---|---|
35=G OrderCancelReplaceRequest | 11 new ClOrdID, 41 the order's ClOrdID (or 37), 44 / 99 new price, 9101 / 9102 new levels (0 removes). Quantity and type cannot change. | 150=5 Replaced; refused: 35=9 with 434=2 |
35=F OrderCancelRequest | 11 new ClOrdID, 41 (or 37) | 150=4 39=4; already filled: 35=9 102=0; unknown: 35=9 102=1 |
35=H OrderStatusRequest | 11 or 37 | 150=I; unknown: 150=I 39=8 103=5 |
8=FIX.4.4 | 35=G | 49=K12 | 56=OCTOLIT | 57=TRADE | 34=3 | 11=ord-20261111-001-r1 | 41=ord-20261111-001 | 44=1.0740 | 9101=1.0690 | 60=20261111-09:31:00.000 |8=FIX.4.4 | 35=F | 49=K12 | 56=OCTOLIT | 57=TRADE | 34=4 | 11=ord-20261111-001-c1 | 41=ord-20261111-001 | 60=20261111-09:32:00.000 |Each replace or cancel carries its own fresh 11; 41 names the order. A refused replace answers 35=9 434=2 - for example when the order is no longer pending; the next ExecutionReport is the truth.
#Positions and open orders
Rebuild your state after every logon, and reconcile it on a timer:
35=AFOrderMassStatusRequest (584request id, optional1/55) → one150=Iper pending order and open position,911total,912=Yon the last.35=ANRequestForPositions (710request id,724=0, optional1/55) →35=AOthen one35=APper open position with721,704/705long / short quantity,730open price and9101/9102.
8=FIX.4.4 | 35=AN | 49=K12 | 56=OCTOLIT | 57=TRADE | 34=6 | 710=pos-1 | 724=0 |Positions also change outside your session - the client in the trader, a stop loss, a take profit, a stop-out. Reconcile with 35=AN on a timer, or watch the account frames of the WebSocket stream, which tell you when something on an account changed.
#Market data (QUOTE session)
-
Subscribe
35=Vwith262request id,263=1(snapshot + updates;0snapshot only,2unsubscribe),264=0,265,267with269=0bid and269=1offer, and146+55per symbol. Up to 100 symbols per session.MarketDataRequest8=FIX.4.4 | 35=V | 49=K12 | 56=OCTOLIT | 57=QUOTE | 34=2 | 262=md-1 | 263=1 | 264=0 | 265=1 | 267=2 | 269=0 | 269=1 | 146=2 | 55=X:EURUSD | 55=X:XAUUSD | -
Snapshot, then increments
One
35=Wper symbol with the current bid and ask, then35=Xincremental refreshes - at most one per symbol every 250 ms. The prices are the trading prices (bid / ask with the account type's spread), the same values the Trading API and the WebSocket deliver.MarketDataSnapshotFullRefresh8=FIX.4.4 | 35=W | 49=OCTOLIT | 50=QUOTE | 56=K12 | 34=2 | 262=md-1 | 55=X:EURUSD | 268=2 | 269=0 | 270=1.07500 | 269=1 | 270=1.07512 | -
Unknown symbol
MarketDataRequestReject8=FIX.4.4 | 35=Y | 49=OCTOLIT | 50=QUOTE | 56=K12 | 34=3 | 262=md-2 | 281=0 | 58=symbol_not_found |Symbol codes are listed by
GET /api/open/v1/symbols; useX:EURUSD, the numeric id orEUR-USD.
#Reject reasons
103 OrdRejReason on a 150=8 report. 58 always carries the REST error code, which is the more precise of the two.
OrdRejReasonRefused by the brokerOrdRejReasonUnknown symbolOrdRejReasonMarket closedOrdRejReasonOrder exceeds limitOrdRejReasonUnknown orderOrdRejReasonDuplicate ClOrdIDOrdRejReasonUnsupported order characteristicOrdRejReasonIncorrect quantityOrdRejReasonOther - see tag 58 (REST error code)Session-level problems come as 35=3 Reject (malformed message, bad tag) or 35=5 Logout (key revoked, limit hit); both carry 58.
#Limits
| FIX | |
|---|---|
| Transport | TCP + TLS 1.2 or newer |
| Version | FIX 4.4 |
| HeartBtInt (108) | 5 - 120 s |
| Messages per second per session | 50 (burst 100) |
| Logons per minute per address | 30 |
| Connections per address | 20 |
| Symbols per QUOTE session | 100 |
| Quote updates | at most one X per symbol every 250 ms |
Order guards are shared with REST and counted together: the per-key and per-account order actions per second below apply to FIX orders too, and whatever refuses an order over REST refuses it over FIX, with the same code in 58.
| Trading API | |
|---|---|
| Requests per minute, per key | 1 200 (broker-set, 10 - 12 000) |
| Requests per minute, per client | 2 x the key limit, across all keys |
| Order actions per second, per key | 10 (broker-set, 1 - 50) |
| Order actions per second, per trading account | 10 (broker-set, 1 - 50) |
| Keys per client | 5 (broker-set, 1 - 20) |
| Wrong keys per minute, per address | 60, then 429 too_many_invalid_keys |
| Symbols per GET /quotes | 100 |
| Bars per GET /bars | 5 000 |
| Stream ticket | single use, 60 s |
#Certification checklist
Run this against a demo account before your broker switches the key to live. Each line is one message pair you should be able to show in a log.
- Logon on QUOTE and on TRADE with
141=Y; a refused logon (35=5) is handled without a reconnect storm (backoff, at most 30 logons per minute). - Heartbeats at
108; a TestRequest is answered with the same112. - Market order:
150=0then150=F;721stored. - Limit order, replace (
150=5), cancel (150=4); a cancel of a filled order answers35=9 102=0and is handled. - Reject
150=8with103and58mapped to your error handling; duplicate11gives103=6and is not retried. - Close by
721, partial and full. 35=AFand35=ANafter a reconnect rebuild the same state your system had before.- QUOTE: subscribe to 2 symbols, receive
35=Wthen35=X; unsubscribe with263=2; unknown symbol gives35=Y. - Rate limits respected: no more than 50 messages per second per session, no more than 10 order actions per second per key.
- Clean logout (
35=5) at shutdown.
Build and parse single messages in the FIX message lab; ask your broker for a certification session when the list above is green.