OKX Spot wire format
How OKX spot (SPOT) messages become rows in our Parquet files. Each data type below shows a real message next to the rows we publish for it, with the source and the rule for every column. Hover a line or a column to see how they connect.
Checked against our pipeline
These messages were captured from OKX spot (SPOT) on 2026-10-05. Our test suite replays each one through the production collector parser and postprocessor, and fails if the published rows differ from the rows on this page. Files written before a change to the pipeline can differ; see Known Gaps & Corrections.
Every channel is subscribed per instrument with {"channel": …, "instId": "BTC-USDT"}. OKX Spot uses the same message formats and postprocessor as OKX Futures; spot sizes are in the base asset.
- Public WebSocket (books, tickers):
wss://ws.okx.com:8443/ws/v5/public - Business WebSocket (trades-all):
wss://ws.okx.com:8443/ws/v5/business
Exchange API reference: www.okx.com. Conventions shared by every exchange are on the wire formats overview.
Trades
One row per fill from the trades-all channel.
- WebSocket trades-all
Every trades-all message carries exactly one fill.
{"arg": {"channel": "trades-all","instId": "BTC-USDT"},"data": [{"instId": "BTC-USDT",→ symbol"tradeId": "1066842395",→ trade_id"px": "85668.8",→ price"sz": "0.00015743",→ quantity"side": "buy",→ is_buyer_maker"ts": "1791227699299",→ event_time · trade_time"source": "0"}]}
Published row
event_time.true when side is sell (the taker sold, so the buyer was the maker); false when side is buy.MARKET. A placeholder: OKX does not send an order type.Notes
- Rows come from the per-fill
trades-allchannel on the business endpoint. The publictradeschannel sends one entry per taker order, fill price and order source; we stopped using it in the 2026-09 data-integrity release (see the venue notes). - Rows in a file are sorted by
trade_time, thentrade_id. Atrade_idis published once per hour file. - OKX's
sourcefield (0normal order,1Enhanced Liquidity Program order) is not published.
Column types and descriptions: Trades schema.
Order Book
One row per price level from the 400-level books channel: a snapshot on subscribe, then 100 ms updates.
- WebSocket books
Order book snapshot (action: snapshot)
OKX sends a 400-level snapshot right after subscribing. It anchors the update chain: its prevSeqId is always -1.
Abridged: OKX sent 400 bid and 400 ask levels in this message; the best three of each are shown.
{"arg": {"channel": "books","instId": "BTC-USDT"→ symbol},"action": "snapshot",→ event_type"data": [{"asks": [→ side["85668.8", "0.60167743", "0", "24"],→ side · price · quantity · order_count["85669.8", "0.04202294", "0", "1"],→ side · price · quantity · order_count["85670.7", "0.19024141", "0", "1"]→ side · price · quantity · order_count],"bids": [→ side["85668.7", "0.15616065", "0", "6"],→ side · price · quantity · order_count["85667.8", "0.0000102", "0", "1"],→ side · price · quantity · order_count["85667.4", "0.00001172", "0", "1"]→ side · price · quantity · order_count],"ts": "1791227700007",→ event_time"checksum": 0,"seqId": 82018808615,→ final_update_id"prevSeqId": -1→ last_update_id}]}
Published rows · 6
snapshot when action is snapshot.seqId.prevSeqId, which is always -1 for a snapshot. Kept for files written before prev_final_update_id existed.bid for levels in bids, ask for levels in asks. One row per level.0) and not published.Order book update (action: update)
Incremental changes, pushed every 100 ms while the book changes. Each update's prevSeqId equals the previous message's seqId.
Abridged: OKX sent 11 bid and 6 ask levels in this message; two bids and two asks are shown, including one level removed with quantity 0.
{"arg": {"channel": "books","instId": "BTC-USDT"→ symbol},"action": "update",→ event_type"data": [{"asks": [→ side["85670.3", "0.0000102", "0", "1"],→ side · price · quantity · order_count["85755.7", "0", "0", "0"]→ side · price · quantity · order_count],"bids": [→ side["85659.9", "0.06542374", "0", "3"],→ side · price · quantity · order_count["85657", "0.00448811", "0", "2"]→ side · price · quantity · order_count],"ts": "1791227700107",→ event_time"checksum": 0,"seqId": 82018808663,→ final_update_id"prevSeqId": 82018808615→ prev_final_update_id · last_update_id}]}
Published rows · 4
update when action is update.seqId.prevSeqId. Equals the previous message's final_update_id; a mismatch is a gap.prevSeqId, the same value as prev_final_update_id. Kept for files written before prev_final_update_id existed.bid for levels in bids, ask for levels in asks. One row per level.0 removes the level. Base asset units.Snapshot of an empty book
A snapshot with no levels on either side, captured on the quiet AUDF-USDT pair. It still anchors the sequence chain, so it is published as one marker row with side noop. An update with no levels whose seqId differs from prevSeqId is published the same way, as an update marker row.
{"arg": {"channel": "books","instId": "AUDF-USDT"→ symbol},"action": "snapshot",→ event_type"data": [{"asks": [],"bids": [],"ts": "1791227741701",→ event_time"checksum": 0,"seqId": 696923,→ final_update_id"prevSeqId": -1→ last_update_id}]}
Published row
snapshot when action is snapshot.seqId.prevSeqId (-1 for a snapshot).noop: the message has no levels, so one marker row keeps its sequence ID.0 on the marker row.0 on the marker row.Keepalive update (not published)
When a book has not changed for about 60 seconds, OKX sends an update with no levels and seqId equal to prevSeqId. It changes nothing and is not published. Captured on AUDF-USDT; the same applies to swaps.
{"arg": {"channel": "books","instId": "AUDF-USDT"},"action": "update","data": [{"asks": [],"bids": [],"ts": "1791227760001","checksum": 0,"seqId": 696923,"prevSeqId": 696923}]}
Not published
Notes
- OKX retired the
bookschecksum in 2026: the field is still sent but is always0and is not published.seqId/prevSeqIdis the only continuity check: an update continues the book when itsprev_final_update_idequals the previous message'sfinal_update_id. All rows of one message carry the same two IDs, so check once per message, not per row. - OKX documents that
seqIdcan restart at a lower value after maintenance.prevSeqIdstill equals the previous message'sseqId, so check the link, not thatseqIdincreases. - The collector validates the chain before it writes. After a gap it reconnects and starts again from a new WebSocket snapshot.
- Quantities are in the base asset.
- Before continuous trading starts on a new listing (OKX
openTypecall_auctionorpre_quote), the best ask can be below the best bid. The collector accepts a crossed book only in that window, from the instrument'slistTimetocontTdSwTime(OKXGET /api/v5/public/instruments). - Files can start with a checkpoint snapshot of the book carried over from the previous hour: its
received_timeis the hour start,transaction_timeis null, andfinal_update_idandlast_update_idare both the lastseqIdapplied.
Column types and descriptions: Order Book schema.
Ticker
One row per tickers message.
- WebSocket tickers
{"arg": {"channel": "tickers","instId": "BTC-USDT"},"data": [{"instType": "SPOT","instId": "BTC-USDT",→ symbol"last": "85668.8",→ price_change · price_change_percent · last_price"lastSz": "0.00047965",→ last_quantity"askPx": "85668.8","askSz": "0.60167743","bidPx": "85668.7","bidSz": "0.15659044","open24h": "85389.7",→ price_change · price_change_percent · open_price"high24h": "86994.3",→ high_price"low24h": "84979.5",→ low_price"sodUtc0": "86522.4","sodUtc8": "85267.6","volCcy24h": "557320377.998248583",→ quote_asset_volume"vol24h": "6475.49027556",→ base_asset_volume"ts": "1791227699768"→ event_time}]}
Published row
last − open24h, exact decimal.last − open24h) / open24h × 100, rounded half-even to 8 decimal places. Percent.vol24h: for spot OKX gives it in the base asset.volCcy24h: for spot OKX gives it in the quote asset.Notes
- Best bid and ask (
bidPx,bidSz,askPx,askSz) and the day openssodUtc0andsodUtc8are not published.
Column types and descriptions: Ticker schema.
Not published
- Mark Price: Not collected for OKX spot. Spot markets have no funding rate.
- Open Interest: Spot markets have no open interest.
- Liquidations: We collect liquidations for OKX perpetual swaps only.