Lighter (Perpetuals & Spot) wire format
How Lighter 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 Lighter 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.
Lighter identifies markets by a market index (1 is BTC, 2048 is ETH/USDC). The collector loads the market list from GET /api/v1/orderBooks when it starts and when it checks for new markets. It subscribes to order_book/<index> and trade/<index> for every active market, and once to market_stats/all (perpetuals) and spot_market_stats/all (spot). Those two channels carry every market; rows are published only for the active markets. Perpetual symbols are the base asset (BTC); spot symbols are BASE/QUOTE on Lighter and BASE_QUOTE in our files (ETH_USDC).
- Public WebSocket (order book, trades, market stats):
wss://mainnet.zklighter.elliot.ai/stream - REST (market list):
https://mainnet.zklighter.elliot.ai/api/v1/orderBooks
Exchange API reference: apidocs.lighter.xyz. Conventions shared by every exchange are on the wire formats overview.
Trades
One row per trade from the trade/<index> channel, from both the trades and liquidation_trades arrays.
- WebSocket trade/<index>
Trade update
One frame can carry several trades. Each trade becomes its own row; rows are ordered by trade_time, then trade_id.
{"channel": "trade:1",→ symbol"liquidation_trades": [],"nonce": 23863348794,"trades": [{"trade_id": 33380750090,→ trade_id"trade_id_str": "33380750090","tx_hash": "0000001ea5bde783000001a10d7db4e4000000000000000000000000000000000000000000000000","type": "trade",→ order_type"market_id": 1,"market_kind": "perps","size": "0.00010",→ quantity"price": "85636.3",→ price"usd_amount": "8.563630","ask_id": 562953487717055,"ask_id_str": "562953487717055","bid_id": 844421361202466,"bid_id_str": "844421361202466","ask_client_id": 31938461114236,"ask_client_id_str": "31938461114236","bid_client_id": 5434,"bid_client_id_str": "5434","ask_account_id": 702384,"bid_account_id": 750305,"is_maker_ask": true,→ is_buyer_maker"block_height": 349153116,"timestamp": 1791227704548,→ event_time · trade_time"taker_position_size_before": "-0.00001","taker_entry_quote_before": "0.856509","taker_initial_margin_fraction_before": 500,"taker_position_sign_changed": true,"maker_fee": 28,"maker_position_size_before": "39.75775","maker_entry_quote_before": "3398551.295093","maker_initial_margin_fraction_before": 3333,"transaction_time": 1791227704597066,"ask_order_version": 0,"bid_order_version": 0},{"trade_id": 33380750089,→ trade_id"trade_id_str": "33380750089","tx_hash": "b5313d3bcb15bfa14b7c18ea21330d3e0efdf5ba80874c3e478f9fcfd641d10e248218a5ffabf4ce","type": "trade",→ order_type"market_id": 1,"market_kind": "perps","size": "0.00031",→ quantity"price": "85635.5",→ price"usd_amount": "26.547005","ask_id": 562953487717054,"ask_id_str": "562953487717054","bid_id": 844421361202466,"bid_id_str": "844421361202466","ask_client_id": 34120,"ask_client_id_str": "34120","bid_client_id": 5434,"bid_client_id_str": "5434","ask_account_id": 281474976623775,"bid_account_id": 750305,"is_maker_ask": true,→ is_buyer_maker"block_height": 349153116,"timestamp": 1791227704548,→ event_time · trade_time"taker_position_size_before": "-0.00032","taker_entry_quote_before": "27.408314","taker_initial_margin_fraction_before": 500,"maker_fee": 36,"maker_position_size_before": "1.36744","maker_entry_quote_before": "117314.743225","maker_initial_margin_fraction_before": 2000,"transaction_time": 1791227704596967,"ask_order_version": 0,"bid_order_version": 0}],"type": "update/trade"}
Published rows · 2
timestamp. Milliseconds. The same value as trade_time: the frame has no separate event time.channel (1 is BTC), looked up in the market list the collector loads from GET /api/v1/orderBooks.size as the exact decimal string. Base asset units.timestamp. Milliseconds.is_maker_ask. is_maker_ask: true means the seller was the maker, so the buyer was the aggressor and is_buyer_maker is false. true if Lighter omits is_maker_ask.type: trade for ordinary trades, liquidation for liquidations.Subscription replay with an old liquidation
When the collector subscribes (also after every reconnect), Lighter first sends subscribed/trade with the most recent trades and, separately, the most recent liquidations, which can be hours old. Trades from this frame are published only if their timestamp is no earlier than 5 seconds before the start of the arrival hour, and only if their trade_id was not already published. Here both trades are published and the liquidation from 16:23 UTC, received at 19:15 UTC, is not.
Abridged: Lighter sent 50 trades and 50 liquidations in this frame; two trades and the most recent liquidation are shown.
{"channel": "trade:1",→ symbol"liquidation_trades": [{"trade_id": 33365034814,→ trade_id"trade_id_str": "33365034814","tx_hash": "0000001ea368fe94000001a10ce03f1f000000000000000000000000000000000000000000000000","type": "liquidation",→ order_type"market_id": 1,"market_kind": "perps","size": "0.00873",→ quantity"price": "84943.1",→ price"usd_amount": "741.553263","ask_id": 562953486967141,"ask_id_str": "562953486967141","bid_id": 844421361906196,"bid_id_str": "844421361906196","ask_client_id": 0,"ask_client_id_str": "0","bid_client_id": 152055869430945,"bid_client_id_str": "152055869430945","ask_account_id": 281474976502928,"bid_account_id": 726722,"is_maker_ask": false,→ is_buyer_maker"block_height": 349069394,"timestamp": 1791217385247,→ event_time · trade_time"taker_fee": 10000,"taker_position_size_before": "0.00873","taker_entry_quote_before": "747.818784","taker_initial_margin_fraction_before": 200,"taker_position_sign_changed": true,"maker_fee": 28,"maker_position_size_before": "2.22729","maker_entry_quote_before": "189612.331672","maker_initial_margin_fraction_before": 500,"transaction_time": 1791217385309461,"taker_allocated_margin_usdc_before": 15057329,"ask_order_version": 0,"bid_order_version": 0}],"nonce": 23863347871,"trades": [{"trade_id": 33380746827,→ trade_id"trade_id_str": "33380746827","tx_hash": "0000001ea5bdc7d8000001a10d7dadd0000000000000000000000000000000000000000000000000","type": "trade",→ order_type"market_id": 1,"market_kind": "perps","size": "0.00012",→ quantity"price": "85636.0",→ price"usd_amount": "10.276320","ask_id": 562953487717006,"ask_id_str": "562953487717006","bid_id": 844421361202510,"bid_id_str": "844421361202510","ask_client_id": 1000700396,"ask_client_id_str": "1000700396","bid_client_id": 57912887826612,"bid_client_id_str": "57912887826612","ask_account_id": 711373,"bid_account_id": 27927,"is_maker_ask": false,→ is_buyer_maker"block_height": 349153099,"timestamp": 1791227702736,→ event_time · trade_time"taker_position_size_before": "-0.00020","taker_entry_quote_before": "17.127220","taker_initial_margin_fraction_before": 1000,"maker_fee": 38,"maker_position_size_before": "-8.04322","maker_entry_quote_before": "687471.664393","maker_initial_margin_fraction_before": 500,"transaction_time": 1791227702773021,"ask_order_version": 0,"bid_order_version": 0},{"trade_id": 33380746738,→ trade_id"trade_id_str": "33380746738","tx_hash": "0000001ea5bdc718000001a10d7dadd0000000000000000000000000000000000000000000000000","type": "trade",→ order_type"market_id": 1,"market_kind": "perps","size": "0.00003",→ quantity"price": "85636.4",→ price"usd_amount": "2.569092","ask_id": 562953487717000,"ask_id_str": "562953487717000","bid_id": 844421361202507,"bid_id_str": "844421361202507","ask_client_id": 34116,"ask_client_id_str": "34116","bid_client_id": 0,"bid_client_id_str": "0","ask_account_id": 281474976623775,"bid_account_id": 723636,"is_maker_ask": true,→ is_buyer_maker"block_height": 349153099,"timestamp": 1791227702736,→ event_time · trade_time"taker_fee": 100,"taker_position_size_before": "0.01240","taker_entry_quote_before": "1061.781826","taker_initial_margin_fraction_before": 200,"maker_fee": 36,"maker_position_size_before": "1.36759","maker_entry_quote_before": "117327.611953","maker_initial_margin_fraction_before": 2000,"transaction_time": 1791227702737142,"ask_order_version": 0,"bid_order_version": 0}],"type": "subscribed/trade"}
Published rows · 2
timestamp. Milliseconds. The same value as trade_time: the frame has no separate event time.channel (1 is BTC), looked up in the market list the collector loads from GET /api/v1/orderBooks.size as the exact decimal string. Base asset units.timestamp. Milliseconds.is_maker_ask. is_maker_ask: true means the seller was the maker, so the buyer was the aggressor and is_buyer_maker is false. true if Lighter omits is_maker_ask.type: trade for ordinary trades, liquidation for liquidations.Spot trade (ETH/USDC)
Spot markets use the same channel and mapping. Only the symbol differs: Lighter names spot markets BASE/QUOTE, published as BASE_QUOTE.
{"channel": "trade:2048",→ symbol"liquidation_trades": [],"nonce": 23863586690,"trades": [{"trade_id": 33381085033,→ trade_id"trade_id_str": "33381085033","tx_hash": "0000001ea5caa221000001a10d80fa0e000000000000000000000000000000000000000000000000","type": "trade",→ order_type"market_id": 2048,"market_kind": "spot","size": "0.0001",→ quantity"price": "2713.60",→ price"usd_amount": "0.000000","ask_id": 576742227473933817,"ask_id_str": "576742227473933817","bid_id": 577023702063775400,"bid_id_str": "577023702063775400","ask_client_id": 51016536833,"ask_client_id_str": "51016536833","bid_client_id": 0,"bid_client_id_str": "0","ask_account_id": 314233,"bid_account_id": 255124,"is_maker_ask": true,→ is_buyer_maker"block_height": 349154892,"timestamp": 1791227918862,→ event_time · trade_time"taker_fee": 280,"taker_position_size_before": "0.0000","taker_entry_quote_before": "0.000000","taker_position_sign_changed": true,"maker_fee": 28,"maker_position_size_before": "0.0000","maker_entry_quote_before": "0.000000","maker_position_sign_changed": true,"transaction_time": 1791227918862785,"ask_order_version": 0,"bid_order_version": 0}],"type": "update/trade"}
Published row
timestamp. Milliseconds. The same value as trade_time: the frame has no separate event time.channel (2048 is ETH/USDC), looked up in the market list the collector loads from GET /api/v1/orderBooks, with / replaced by _.size as the exact decimal string. Base asset units (ETH).timestamp. Milliseconds.is_maker_ask. is_maker_ask: true means the seller was the maker, so the buyer was the aggressor and is_buyer_maker is false. true if Lighter omits is_maker_ask.type: trade for ordinary trades, liquidation for liquidations.Notes
- Lighter occasionally sends a trade a second time, seconds later, in an ordinary
update/tradeframe. A repeatedtrade_idis always a re-delivery, becausetrade_idis one counter across all markets. Only the first delivery is published, also when the repeat arrives in the next hour. - Files are keyed by arrival hour (
received_time). Lighter's trade feed can fall behind by seconds, occasionally minutes, so a late trade can appear in the next hour's file. Filter ontrade_timeand load the following hour when you need a complete window. order_typeis copied as sent, so other trade types Lighter documents (deleverage,market-settlement) appear under their own name.- Files written before the October 2026 pipeline update contain no rows from
liquidation_tradesand no trades fromsubscribed/tradereplays. Liquidation fills sent only inliquidation_trades, and trades missed across reconnects, are absent there.
Column types and descriptions: Trades schema.
Order Book
One row per price level from the order_book/<index> channel: the subscribed/order_book snapshot, then update/order_book updates.
- WebSocket order_book/<index>
Subscription snapshot
Lighter sends the book as subscribed/order_book when the collector subscribes, including after every reconnect. Each snapshot becomes one snapshot row per level and starts a new nonce chain.
Abridged: Lighter sent 1821 bid and 1271 ask levels in this snapshot; the best two of each are shown.
{"channel": "order_book:1",→ symbol"last_updated_at": 1791227703748044,→ transaction_time"offset": 4577935,→ last_update_id"order_book": {"code": 0,"asks": [→ side{→ side"price": "85635.5",→ side · price"size": "0.03539"→ side · quantity},{→ side"price": "85635.7",→ side · price"size": "0.01700"→ side · quantity}],"bids": [→ side{→ side"price": "85635.3",→ side · price"size": "0.00020"→ side · quantity},{→ side"price": "85635.2",→ side · price"size": "0.00020"→ side · quantity}],"offset": 4577935,"nonce": 23863347847,→ final_update_id"last_updated_at": 1791227703748044,"begin_nonce": 0},"timestamp": 1791227703769,→ event_time"type": "subscribed/order_book"→ event_type}
Published rows · 4
timestamp, when Lighter's API server published the frame. Milliseconds.last_updated_at, Lighter's time of the last book update in microseconds, divided by 1,000 and truncated to milliseconds.channel (1 is BTC), looked up in the market list the collector loads from GET /api/v1/orderBooks.snapshot for subscribed/order_book.begin_nonce 0 in snapshots; it is not a link to an earlier message.order_book.offset. It orders messages but is not contiguous, and it restarts when a reconnect lands on another server.bid for levels in bids, ask for levels in asks. One row per level.size as the exact decimal string. Base asset units.Book update
The first update after the snapshot above: its begin_nonce equals the snapshot's nonce. This one changes only asks; bids is empty.
{"channel": "order_book:1",→ symbol"last_updated_at": 1791227703793710,→ transaction_time"offset": 4577939,→ last_update_id"order_book": {"code": 0,"asks": [→ side{→ side"price": "85635.5",→ side · price"size": "0.00043"→ side · quantity},{→ side"price": "85635.7",→ side · price"size": "0.00000"→ side · quantity},{→ side"price": "85636.0",→ side · price"size": "0.00000"→ side · quantity},{→ side"price": "85640.9",→ side · price"size": "0.29572"→ side · quantity}],"bids": [],→ side"offset": 4577939,"nonce": 23863347898,→ final_update_id"last_updated_at": 1791227703793710,"begin_nonce": 23863347847→ prev_final_update_id},"timestamp": 1791227703809,→ event_time"type": "update/order_book"→ event_type}
Published rows · 4
timestamp, when Lighter's API server published the frame. Milliseconds.last_updated_at, Lighter's time of the last book update in microseconds, divided by 1,000 and truncated to milliseconds.channel (1 is BTC), looked up in the market list the collector loads from GET /api/v1/orderBooks.update for update/order_book.nonce of the previous message (snapshot or update); a mismatch is a gap.order_book.offset. It orders messages but is not contiguous, and it restarts when a reconnect lands on another server.bid for levels in bids, ask for levels in asks. One row per level.size as the exact decimal string: the new total at that price, base asset units. 0.00000 removes the level.Notes
- Each update's
prev_final_update_id(begin_nonce) equals the previous message'sfinal_update_id(nonce). A snapshot starts a new chain. A mismatch is a gap: resume from the next snapshot. - A message with no levels at all is published as one row with
sidenoop,price0andquantity0, so thenoncechain stays complete. Anoopupdate changes no level. Anoopsnapshot means the book is empty: clear your book. last_update_id(offset) restarts when a reconnect lands on another Lighter server, so a lower value after a reconnect is expected. Use thenoncechain to detect gaps.- Files written before the 2026-09 data-integrity release hold the
offsetin bothfinal_update_idandlast_update_id, andprev_final_update_idis empty. - Lighter's REST order list (
GET /api/v1/orderBookOrders) carries nononce, so it cannot anchor the update stream and is not published. Files written before the October 2026 pipeline update can containsnapshotrows built from it when the collector had REST snapshots enabled; do not use those rows to anchor the book. - Files written before the October 2026 pipeline update have our receive time in milliseconds as
event_timeand an emptytransaction_time. - 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_idis thenonceof the last message applied. In these rowslast_update_idalso holds thatnonce, not anoffset.
Column types and descriptions: Order Book schema.
Ticker
One row per market per market_stats/all or spot_market_stats/all frame that includes the market.
- WebSocket market_stats/all
- WebSocket spot_market_stats/all
Perpetual market stats
Abridged: This update/market_stats frame carried 99 of the 235 markets in the subscription snapshot; only BTC (market index 1) is shown. Each market becomes its own row.
{"channel": "market_stats:all","market_stats": {"1": {"symbol": "BTC","market_id": 1,→ symbol"index_price": "85667.7","mark_price": "85635.1","mid_price": "85635.4","best_ask_price": "85635.5","best_bid_price": "85635.3","open_interest": "163238852.201930","open_interest_limit": "72057594037927936.000000","funding_clamp_small": "0.0500","funding_clamp_big": "4.0000","last_trade_price": "85631.6",→ last_price"current_funding_rate": "0.0012","funding_rate": "0.0012","funding_timestamp": 1791226800002,"daily_base_token_volume": 9275.68025,→ base_asset_volume"daily_quote_token_volume": 797148325.667363,→ quote_asset_volume"daily_price_low": 84940.2,→ low_price"daily_price_high": 86955.4,→ high_price"daily_price_change": -0.20010862040431965,→ price_change_percent"base_interest_rate": "0.0100","premium": "-0.0277"}},"timestamp": 1791227704229,→ event_time"type": "update/market_stats"}
Published row
timestamp. Milliseconds.market_id in the market list the collector loads from GET /api/v1/orderBooks.-0.2 means −0.2 %.Spot market stats (ETH/USDC)
Spot markets stream on spot_market_stats/all, which has no mark price, funding or open interest. They publish ticker rows only.
Abridged: This subscribed/spot_market_stats frame carried 11 spot markets; only ETH/USDC (market index 2048) is shown.
{"channel": "spot_market_stats:all","spot_market_stats": {"2048": {"symbol": "ETH/USDC","market_id": 2048,→ symbol"index_price": "2709.710000","mid_price": "2709.70","best_ask_price": "2709.70","best_bid_price": "2709.69","last_trade_price": "2709.69",→ last_price"daily_base_token_volume": 80.6425,→ base_asset_volume"daily_quote_token_volume": 218436.236399,→ quote_asset_volume"daily_price_low": 2681.47,→ low_price"daily_price_high": 2751.24,→ high_price"daily_price_change": 0.08791013958504937→ price_change_percent}},"timestamp": 1791227703768,→ event_time"type": "subscribed/spot_market_stats"}
Published row
timestamp. Milliseconds.market_id (2048 is ETH/USDC) in the market list the collector loads from GET /api/v1/orderBooks, with / replaced by _.-0.2 means −0.2 %.Notes
- The market stats channels have no 24-hour window boundaries, open price, trade count or trade IDs; those columns are null.
- Files written before the October 2026 pipeline update have our receive time in nanoseconds as
event_timeinstead of Lighter's publication time in milliseconds.
Column types and descriptions: Ticker schema.
Mark Price
One row per perpetual market per market_stats/all frame that includes the market.
- WebSocket market_stats/all
Abridged: This update/market_stats frame carried 99 of the 235 markets in the subscription snapshot; only BTC (market index 1) is shown. Each market becomes its own row.
{"channel": "market_stats:all","market_stats": {"1": {"symbol": "BTC","market_id": 1,→ symbol"index_price": "85667.7",→ index_price"mark_price": "85635.1",→ mark_price"mid_price": "85635.4","best_ask_price": "85635.5","best_bid_price": "85635.3","open_interest": "163238852.201930","open_interest_limit": "72057594037927936.000000","funding_clamp_small": "0.0500","funding_clamp_big": "4.0000","last_trade_price": "85631.6","current_funding_rate": "0.0012",→ funding_rate"funding_rate": "0.0012","funding_timestamp": 1791226800002,→ next_funding_time"daily_base_token_volume": 9275.68025,"daily_quote_token_volume": 797148325.667363,"daily_price_low": 84940.2,"daily_price_high": 86955.4,"daily_price_change": -0.20010862040431965,"base_interest_rate": "0.0100","premium": "-0.0277"}},"timestamp": 1791227704229,→ event_time"type": "update/market_stats"}
Published row
timestamp. Milliseconds.market_id in the market list the collector loads from GET /api/v1/orderBooks.current_funding_rate divided by 100. Lighter sends percent per hour (0.0012 is 0.0012 %); we publish a fraction per hourly funding period (0.000012). current_funding_rate is Lighter's estimate of the upcoming payment; funding_rate, the last payment, is used only when it is missing.funding_timestamp. Milliseconds. funding_timestamp is when the last funding was applied, on the hour or a few milliseconds after it; Lighter settles funding every hour.Notes
- Spot markets publish no mark price rows.
- Files written before the 2026-09 data-integrity release had
funding_ratein percent andnext_funding_timeset to the last funding time; they were corrected on 2026-09-30. - Files written before the October 2026 pipeline update have our receive time in nanoseconds as
event_timeinstead of Lighter's publication time in milliseconds.
Column types and descriptions: Mark Price schema.
Open Interest
One row per perpetual market per market_stats/all frame that includes the market.
- WebSocket market_stats/all
Abridged: This update/market_stats frame carried 99 of the 235 markets in the subscription snapshot; only BTC (market index 1) is shown. Each market becomes its own row.
{"channel": "market_stats:all","market_stats": {"1": {"symbol": "BTC","market_id": 1,→ symbol"index_price": "85667.7","mark_price": "85635.1",→ sum_open_interest"mid_price": "85635.4","best_ask_price": "85635.5","best_bid_price": "85635.3","open_interest": "163238852.201930",→ sum_open_interest · sum_open_interest_value"open_interest_limit": "72057594037927936.000000","funding_clamp_small": "0.0500","funding_clamp_big": "4.0000","last_trade_price": "85631.6","current_funding_rate": "0.0012","funding_rate": "0.0012","funding_timestamp": 1791226800002,"daily_base_token_volume": 9275.68025,"daily_quote_token_volume": 797148325.667363,"daily_price_low": 84940.2,"daily_price_high": 86955.4,"daily_price_change": -0.20010862040431965,"base_interest_rate": "0.0100","premium": "-0.0277"}},"timestamp": 1791227704229,→ timestamp"type": "update/market_stats"}
Published row
market_id in the market list the collector loads from GET /api/v1/orderBooks.open_interest divided by mark_price, rounded half-even to the market's size precision (supported_size_decimals from GET /api/v1/orderBooks, 5 for BTC), with trailing zeros removed: 1906.21430 is published as 1906.2143. Base asset units.timestamp. Milliseconds.Notes
- Lighter's
open_interestis the USD notional, not base units: base open interest fromGET /api/v1/orderBookDetailsmultiplied by the mark price matches it. - Spot markets publish no open interest rows.
- Files written before the 2026-09 data-integrity release had the USD notional in
sum_open_interest; they were corrected on 2026-09-30. - Files written before the October 2026 pipeline update have our receive time in nanoseconds as
timestampinstead of Lighter's publication time in milliseconds.
Column types and descriptions: Open Interest schema.
Not published
- Liquidations: Lighter has no separate liquidation stream. Liquidation fills arrive on the trade channel and are published as trades with
order_typeliquidation.