Lighter API and Python SDK guide

The Lighter API is a signed-transaction API, not a key-and-secret REST API. You install the official Python SDK (pip install lighter-sdk), look up your integer account index, register an API key at an index of your choice with one wallet signature, and then sign orders locally and send them over REST (sendTx) or WebSocket. On the free Standard account you get 0% fees but only 60 requests per minute; the opt-in Premium account cuts taker latency to 140 ms and allows 4,000+ order transactions per minute for 0.0280% taker / 0.0040% maker. Market data (trades, order books, candles, funding) needs no key at all: see market data endpoints.

Cheat sheet (September 25, 2026). REST: https://mainnet.zklighter.elliot.ai/api/v1/ · WebSocket: wss://mainnet.zklighter.elliot.ai/stream · Chain ID 304 (testnet 300) · SDKs: Python lighter-sdk 1.1.4, Go lighter-go, TypeScript demo lighter-ts · Auth tokens: max 8 hours · Read-only tokens: 1 day to 10 years · Order expiry: 5 minutes to 30 days · Colocation: AWS Tokyo ap-northeast-1a · WebSocket keepalive: a frame every 2 minutes · Order-book updates: every 50 ms, with begin_nonce continuity · Market data (trades, order book, candles, funding): public, no key (section 7).

Open a Lighter account for your bot

Sign up with code 718610TD. Standard trading is free, and if your bot needs Premium speed, your first week of Premium fees is rebated (cap $10M volume).

Open Lighter →

We earn a commission when you sign up through our links; it never raises your fees.

1. How the Lighter API works

Lighter is a zk-rollup perpetuals exchange: orders are matched by a sequencer and settled with proofs on Ethereum. That design shapes the API. There is no "API secret" that you paste into an HMAC function. Instead, every account (and every sub-account) has numbered API key slots. Each slot holds a public key registered on the exchange; you keep the private key and use it to sign transactions such as create, modify and cancel order. The exchange checks the signature and the nonce and forwards the transaction to the sequencer.

The Python SDK does the signing through a compiled signer binary built from the public lighter-go repo (you can compile it yourself with the repo's justfile). Reads are ordinary REST calls; some of them, and the private WebSocket channels, need a short-lived auth token that you also derive from the API private key. If you have used Binance or Bybit, the mental switch is: keys sign transactions; tokens authenticate reads.

If you are new to decentralised exchanges, what is a DEX explains why there is no account login in the usual sense: the Ethereum wallet that created the account is the root of trust.

2. Official SDKs and endpoints

ToolInstall / URLWhat it is
Python SDKpip install lighter-sdk (1.1.4 on PyPI) · github.com/elliottech/lighter-pythonOfficial. REST API classes (AccountApi, OrderApi, TransactionApi…), SignerClient, a simple WsClient and ~20 order examples. Apache-2.0, Python 3.8+.
Go SDKgithub.com/elliottech/lighter-goOfficial. Signing code, transaction types and constants; the source of the Python signer binary.
TypeScriptgithub.com/elliottech/lighter-tsOfficial "client demo": the app's client-side state store, WebSocket pipeline, trading maths and types. A reference, not a full trading SDK.
REST (mainnet)https://mainnet.zklighter.elliot.ai/api/v1/Market data, account data, sendTx / sendTxBatch. Chain ID 304.
WebSocket (mainnet)wss://mainnet.zklighter.elliot.ai/streamOrder books, trades, candles, account streams; can also send signed transactions.
Testnethttps://testnet.zklighter.elliot.ai · wss://testnet.zklighter.elliot.ai/streamChain ID 300. Test signing, nonces and precision here first.
Explorer APIhttps://explorer.elliot.ai/api/Block-explorer data (batches, blocks, logs, accounts); its own limit of 90 weighted requests per minute. What each field means: our Lighter explorer guide.

Sources: apidocs.lighter.xyz Get Started, SDK and Rate Limits pages; PyPI and GitHub, checked September 25, 2026. The docs recommend AWS Tokyo ap-northeast-1a (apne1-az4) for colocation; direct access that skips CloudFront is offered to Premium accounts on the top staking tier on request. API changes are announced in Lighter's Telegram API channel (t.me/lighter_api_updates) and #api-updates on Discord.

3. Account index, API keys and tokens

Account index

Lighter addresses accounts by an integer account index. One Ethereum (L1) address can own a main account and sub-accounts, each with its own index, margin, positions and API key set. You fetch them once with AccountApi.accounts_by_l1_address. Sub-accounts: up to 4 on Standard, 16 on Plus, 64 on Premium (apidocs Account Types).

API key index

Each account has key slots numbered 0–254. The Get Started page (updated September 24, 2026) says indices 0–1 are reserved for the web and mobile apps and you can create up to 253 keys at 2–254; the older API keys page (June 2026) still says 0–3 are reserved. Use 4 or higher and you are safe either way. Index 255 is a special value for the apikeys call that returns all keys. Every key has its own nonce, so give each bot process (or each order type) its own key.

Permissions

  • An API key can read auth-gated data, trade, and sign secure withdrawals, but those only go to the L1 address that created the account.
  • Fast withdrawals and transfers to other addresses also need the Ethereum wallet's signature.
  • Binding a new key to the account (ChangePubKey) needs the wallet's signature once, through the SDK or the Ethereum gateway page for multisigs.

Auth tokens and read-only tokens

create_auth_token_with_expiry() produces a token for authenticated REST calls and private WebSocket channels; the default lifetime is 10 minutes and the maximum is 8 hours. A read-only token (1 day to 10 years, created with the tokens/create endpoint or at app.lighter.xyz/read-only-tokens) reads the same data but cannot sign anything, which makes it the right credential for dashboards and monitoring servers.

Maker-only keys (Premium)

Premium accounts can mark up to 251 keys per account index as maker-only. Those keys may only send post-only orders, modify post-only orders and cancel, and in return they use an optimised path with no speed bump. App-reserved keys cannot be maker-only, and changes have a one-hour cooldown.

4. Your first order in Python, step by step

Step 1: create and fund the account

Connect an Ethereum wallet at app.lighter.xyz (sign-up walkthrough: Lighter sign-up guide) and deposit at least the minimum (for example $5 from an EVM chain on a new account; see the deposit and withdrawal guide). Only accounts that have deposited get an account index.

Step 2: install the SDK

pip install lighter-sdk
# Go: go get github.com/elliottech/lighter-go

Step 3: find your account index

import asyncio, lighter

BASE_URL = "https://mainnet.zklighter.elliot.ai"

async def main():
    api = lighter.ApiClient(lighter.Configuration(host=BASE_URL))
    resp = await lighter.AccountApi(api).accounts_by_l1_address(l1_address="0xYourAddress")
    for acc in resp.sub_accounts:
        print(acc.index)
    await api.close()

asyncio.run(main())

Step 4: register an API key

Run examples/system_setup.py from the lighter-python repo (its default API_KEY_INDEX is 3, so change it to 4 or higher first). It creates keypairs with lighter.create_api_key(), signs change_api_key with your Ethereum key for the index you choose, waits, and checks the key with check_client(). Run it on a trusted machine, copy the API private key into your bot's secret store, and do not leave the Ethereum key on the bot server.

Step 5: read the market's precision

Prices and sizes are integers. GET /api/v1/orderBookDetails tells you how many decimals each market uses. On September 25, 2026 (08:23 UTC) the ETH perp (market 0) had size_decimals 4, price_decimals 2, a minimum of 0.0020 ETH and $10, and last traded at $2,689.49. BTC (market 1) used 5 and 1 decimals with a 0.00007 BTC minimum. So 0.0040 ETH is base_amount=40 and $2,600.00 is price=260000.

Step 6: sign and send a post-only limit order

import asyncio, os, lighter

BASE_URL = "https://mainnet.zklighter.elliot.ai"
ACCOUNT_INDEX = int(os.environ["LIGHTER_ACCOUNT_INDEX"])
KEY_INDEX = int(os.environ["LIGHTER_API_KEY_INDEX"])
KEY = os.environ["LIGHTER_API_PRIVATE_KEY"]

async def main():
    client = lighter.SignerClient(
        url=BASE_URL,
        account_index=ACCOUNT_INDEX,
        api_private_keys={KEY_INDEX: KEY},
    )
    err = client.check_client()
    if err is not None:
        raise SystemExit(err)

    tx, tx_hash, err = await client.create_order(
        market_index=0,              # ETH perp
        client_order_index=1001,     # your id, unique across markets
        base_amount=40,              # 0.0040 ETH (about $10.8)
        price=260000,                # $2,600.00
        is_ask=False,                # buy
        order_type=client.ORDER_TYPE_LIMIT,
        time_in_force=client.ORDER_TIME_IN_FORCE_POST_ONLY,
        reduce_only=False,
        order_expiry=client.DEFAULT_28_DAY_ORDER_EXPIRY,
    )
    print(tx_hash, err)

    # cancel it again using the same id
    tx, tx_hash, err = await client.cancel_order(market_index=0, order_index=1001)
    print(tx_hash, err)
    await client.close()

asyncio.run(main())

For a market order, use ORDER_TYPE_MARKET with ORDER_TIME_IN_FORCE_IMMEDIATE_OR_CANCEL and DEFAULT_IOC_EXPIRY; the price then means the worst price you accept, and the sequencer cancels the order if it cannot fill at that price or better. The SDK also has helpers such as create_market_order_limited_slippage. Run the same script against testnet first, then send one minimum-size mainnet order and confirm it under your account index before you let a loop run.

5. Signing, nonces, order types and expiry

  • Nonces are per API key. Each transaction must use the previous nonce + 1 unless you set skip_nonce. The SDK manages this (optimistic nonce manager by default); you can fetch the next one with TransactionApi.next_nonce.
  • When does the nonce move? If the API server rejects a transaction, the nonce does not increase. If the server accepts it (code 200) and the sequencer rejects it later, the nonce does increase. Maker orders can fail at the API level (for example, not enough margin); taker orders with valid syntax go through and fail only at the sequencer.
  • Order types: limit (0), market (1), stop-loss (2), stop-loss limit (3), take-profit (4), take-profit limit (5), TWAP (6). Stop and take-profit orders need a trigger price and a price that caps slippage.
  • Time in force: immediate-or-cancel (0), good-till-time (1), post-only (2).
  • Expiry: a millisecond timestamp between 5 minutes and 30 days ahead; the SDK default for resting orders is 28 days.
  • client_order_index is a uint48 you choose, unique across all markets, so you can cancel without looking up the exchange's id.
  • order_version (optional) makes the sequencer reject a modify whose version is not higher than the stored one, which protects against out-of-order modifies.
  • Batches: sendTxBatch takes several signed transactions; all of them must use the same account and API key. Over WebSocket a batch holds up to 15 transactions.

Testnet works? Go live on Lighter

Create your mainnet account with code 718610TD, deposit, register a key, and run your bot on the free Standard tier or switch to Premium with the first week's fees rebated.

Create a Lighter account →

6. The WebSocket API: live streams and orders

Use REST for setup and occasional reads (market details, account snapshots, history) and the WebSocket for anything that changes every second: order books, trades, funding and your own orders and fills. One connection can carry up to 500 subscriptions, and it can also send signed transactions, so most bots need exactly one socket.

Endpoint, connection and subscribing

  • Mainnet: wss://mainnet.zklighter.elliot.ai/stream · testnet: wss://testnet.zklighter.elliot.ai/stream. From a restricted region, append ?readonly=true to read public data. permessage-deflate compression is supported.
  • On connect the server sends {"session_id": "…", "type": "connected"}.
  • Subscribe with {"type": "subscribe", "channel": "order_book/1"} and unsubscribe with "type": "unsubscribe" and the same channel. Private channels add "auth": "<token>".
  • Replies use a colon and a type prefix: you subscribe to order_book/1 and receive "channel": "order_book:1" with type subscribed/order_book (the snapshot) and then update/order_book. Route messages on type.
  • Markets and accounts are integers: market 0 is ETH and market 1 is BTC (orderBookDetails lists all of them); accounts are your account index, not your wallet address.

Channel reference

Channel (subscribe as)What you getAuth token?
order_book/MARKETFull snapshot on subscribe, then changed price levels in batches every 50 ms. Each update carries begin_nonce and nonce for continuity checks.No
ticker/MARKETBest bid and offer, pushed on every order-book nonce changeNo
trade/MARKETPublic trades, with liquidation trades in a separate liquidation_trades arrayNo
market_stats/MARKET or market_stats/allIndex, mark and mid price, open interest, current_funding_rate (estimate for the next payment) and funding_rate (last payment), 24h volume and rangeNo
candle/MARKET/1m · mark_price_candle/MARKET/1mLive OHLCV (resolutions 1m, 5m, 15m, 30m, 1h, 4h, 12h, 1d), batched every 500 msNo
spot_market_stats/MARKET or /allThe same stats for spot marketsNo
heightCurrent block heightNo
account_all/ACCOUNTPositions, assets, trades, funding history and volume counters for every market of one account; each new subscribe message returns a fresh snapshotNo
account_all_trades/ACCOUNT · account_all_positions/ACCOUNT · user_stats/ACCOUNTAll trades, all positions, account statsNo
account_market/MARKET/ACCOUNTOne account’s position, orders and trades in one marketYes
account_orders/MARKET/ACCOUNT · account_all_orders/ACCOUNTYour open and updated orders, with status such as filled, canceled-post-only or canceled-too-much-slippageYes
account_tx/ACCOUNT · notification/ACCOUNTYour transactions; liquidation, deleverage and announcement notificationsYes
account_all_assets/ACCOUNT · account_spot_avg_entry_prices/ACCOUNT · pool_data · pool_info · rfqSpot balances and average entry prices, public-pool data, RFQs (whitelist only)Yes

From apidocs.lighter.xyz WebSocket reference (updated August 11, 2026), checked September 25, 2026. Channels marked “No” are readable for any account index, so treat your positions as public information.

Authentication for private channels

Private channels take the same short-lived auth token as authenticated REST calls: token, err = client.create_auth_token_with_expiry() on a SignerClient built with your API key. The default lifetime is 10 minutes and the maximum is 8 hours; generate a fresh token every time you reconnect rather than reusing an old one. For a dashboard that should never be able to trade, use a read-only token (1 day to 10 years) instead.

Keepalive, limits and disconnects

  • Keepalive: send at least one frame every 2 minutes — a WebSocket ping frame or any message. {"type":"ping"} is answered with {"type":"pong"}. Silent sockets are closed, and clients that fall behind reading messages may be disconnected more aggressively.
  • Limits per IP: 255 connections, 255 new connections per minute, 500 subscriptions per connection and 5,000 in total, 500 unique accounts per connection, 200 client messages per minute and 50 in-flight messages (transactions excluded). Going over returns errors 23000–23003 (too many requests, subscriptions, accounts or connections).
  • Reconnects happen: zero-downtime deployments cut connections. The order-book offset belongs to the API server and can jump when you land on a different one; the nonce belongs to the matching engine. After any reconnect, resubscribe and rebuild from the new snapshot.

Keeping a local order book correct

The order_book channel sends a full snapshot once, then only changed levels; a level with size 0 is gone. To prove you missed nothing, check that each update’s begin_nonce equals the previous update’s nonce. If it does not, throw the book away and resubscribe. In a 30-second test on the BTC market (09:59 UTC, September 25, 2026) we received 521 order-book updates (about 17 per second), 614 ticker, 115 trade and 21 market-stats messages on one read-only connection, and all 520 continuity checks passed.

A reconnecting Python client

The SDK’s WsClient handles only order_book and account_all, raises an exception on any other message type or when the socket closes, and does not reconnect. For production, a small client of your own is safer. This one keeps a local book with the nonce check, pings every minute, and reconnects with capped exponential backoff and jitter:

import asyncio, json, random, websockets   # pip install websockets

WS_URL = "wss://mainnet.zklighter.elliot.ai/stream"
MARKET = 1           # BTC perp (market index from orderBookDetails)
ACCOUNT = 12345      # your account index
AUTH = None          # auth token for private channels (see below), or None

book = {"bids": {}, "asks": {}}

def apply(side, levels):
    for lv in levels:
        px, sz = lv["price"], float(lv["size"])
        if sz == 0:
            book[side].pop(px, None)      # size 0 removes the level
        else:
            book[side][px] = sz

async def keepalive(ws):
    while True:
        await asyncio.sleep(60)           # server drops a socket silent for 2 minutes
        await ws.send(json.dumps({"type": "ping"}))

async def stream():
    subs = [f"order_book/{MARKET}", f"trade/{MARKET}", f"account_all/{ACCOUNT}"]
    backoff = 1
    while True:
        try:
            async with websockets.connect(WS_URL, max_size=None) as ws:
                for ch in subs:
                    await ws.send(json.dumps({"type": "subscribe", "channel": ch}))
                if AUTH:
                    await ws.send(json.dumps({"type": "subscribe",
                        "channel": f"account_orders/{MARKET}/{ACCOUNT}", "auth": AUTH}))
                ka = asyncio.create_task(keepalive(ws))
                last_nonce, backoff = None, 1
                try:
                    async for raw in ws:
                        msg = json.loads(raw)
                        kind = msg.get("type", "")
                        if kind == "subscribed/order_book":          # full snapshot
                            ob = msg["order_book"]
                            book["bids"].clear(); book["asks"].clear()
                            apply("bids", ob["bids"]); apply("asks", ob["asks"])
                            last_nonce = ob["nonce"]
                        elif kind == "update/order_book":            # deltas every 50 ms
                            ob = msg["order_book"]
                            if ob["begin_nonce"] != last_nonce:
                                raise ConnectionError("book gap: resubscribe for a new snapshot")
                            apply("bids", ob["bids"]); apply("asks", ob["asks"])
                            last_nonce = ob["nonce"]
                        elif kind == "update/trade":
                            for t in msg["trades"]:
                                print("trade", t["price"], t["size"])
                        elif kind.endswith("account_all"):
                            print("positions", list(msg.get("positions", {}).keys()))
                finally:
                    ka.cancel()
        except Exception as exc:
            print("reconnecting:", exc)
            await asyncio.sleep(min(backoff, 30) + random.random())
            backoff *= 2

asyncio.run(stream())

Keep order routing in a separate task or process from this reader so a slow consumer can never delay a cancel, and treat the stream as your live view but REST (account, orders) as the source of truth after any gap.

Sending orders over the WebSocket

Signed transactions can go over the same socket: {"type": "jsonapi/sendtx", "data": {"id": "…", "tx_type": …, "tx_info": …}}, or jsonapi/sendtxbatch with tx_types and tx_infos for up to 15 transactions in one message. tx_info comes from the SignerClient’s sign methods (the repo’s create_modify_cancel_order_ws.py and send_batch_tx_ws.py examples show the flow). They count against the same transaction limits as REST sendTx, and the id you pass comes back in the response so you can match it. As with REST, an accepted transaction is not a fill: confirm on account_orders or account_all.

7. Market data endpoints: trades, order book, funding, candles, liquidations

All Lighter market data is public: no API key, no auth token, no account. REST calls go to https://mainnet.zklighter.elliot.ai/api/v1/ and streams to the WebSocket from section 6. Markets are integers (0 is ETH, 1 is BTC; orderBookDetails lists all of them with their decimals), and prices and sizes come back as strings. On a free Standard account every REST call counts against the same 60 requests a minute as your orders, so poll for backfill and stream everything live.

DataREST (GET, public)WebSocket channelNotes
Markets and precisionorderBookDetails · orderBooks?filter=perp–size and price decimals, minimum order, status, fees; cache at start-up
Order book (price levels)–order_book/MARKET · ticker/MARKET (best bid and offer)snapshot, then changed levels every 50 ms; size 0 removes a level
Order book (order by order)orderBookOrders?market_id=1&limit=250–each resting order with its order_index, owner account index, remaining size, price and expiry; limit up to 250
Recent tradesrecentTrades?market_id=1&limit=100trade/MARKETlimit up to 100; weight 600 on Premium/Plus
Trade historytrades?market_id=1&sort_by=timestamp&limit=100–paged with next_cursor; filters type (trade, liquidation, deleverage, market-settlement), role, from; weight 200
Candlescandles · markPriceCandlescandle/MARKET/1m · mark_price_candle/MARKET/1mparams market_id, resolution (1m to 1d), start_timestamp, end_timestamp, count_back; at most 500 per call; live candles batched every 500 ms
Fundingfundings · funding-ratesmarket_stats/MARKET or market_stats/allfundings takes the same params as candles with resolution 1h or 1d, up to 750 payments per call; funding-rates is a snapshot across Lighter, Binance, Bybit and Hyperliquid
Liquidations (market-wide)trades?…&type=liquidationtrade/MARKET → liquidation_tradespublic; the WebSocket array is the reliable feed (see below)
Liquidations (your account)liquidations?account_index=…&limit=100notification/ACCOUNTneeds an auth token
Mark and index price, open interest, 24h volumeorderBookDetails?market_id=1 · exchangeStats (24h stats for every symbol)market_stats/allthe next-funding estimate (current_funding_rate) is on the WebSocket

Sources: apidocs.lighter.xyz Market data and Historical Data guides (updated September 29, 2026), the API reference for each endpoint and the WebSocket reference (updated October 7, 2026). Every REST call above was made on October 15, 2026; weights are from the Rate Limits page and apply to Premium and Plus (other endpoints weigh 300).

Fields and units that trip people up

  • Funding: rate is a percentage for one hourly payment, and direction says who pays (long means longs pay shorts). fundings rows carry the timestamp in seconds; candles and trades use milliseconds. In market_stats, current_funding_rate estimates the next payment and funding_rate is the last one paid.
  • Candles use one-letter keys: t open time, o h l c, v base volume, V quote (USD) volume, i last trade id. Zero values are omitted, and the last candle is the one still open.
  • Trades include usd_amount, both order ids and account ids, is_maker_ask and the fees each side paid, as integers in millionths of notional (28 = 0.0028%), the same values the Lighter explorer shows in percent.
  • Liquidations: in our test on October 15, 2026, trades with type=liquidation returned only the latest liquidation fill and no cursor, while plain trades paged normally. For a complete feed, record the liquidation_trades array of the trade channel, or page through all trades and filter on type. The per-account liquidations endpoint answers code 20001 without an auth token.
  • Order-level data: orderBookOrders is the only public view of individual resting orders and their owners. The WebSocket book is aggregated by price.

Example: funding, trades and candles in Python

This script ran on October 15, 2026 against mainnet with no key. It sums the last 24 hourly funding payments on BTC, pages through 300 public trades with next_cursor, and reads 48 hourly candles.

import time, requests   # pip install requests

BASE = "https://mainnet.zklighter.elliot.ai/api/v1"
MARKET = 1                                   # BTC perp (ids from orderBookDetails)
now_ms = int(time.time() * 1000)

def get(path, **params):
    r = requests.get(f"{BASE}/{path}", params=params, timeout=10)
    r.raise_for_status()
    return r.json()

# 1) last 24 hourly funding payments (rate is in percent; direction "long" = longs pay)
f = get("fundings", market_id=MARKET, resolution="1h",
        start_timestamp=now_ms - 24 * 3600 * 1000, end_timestamp=now_ms, count_back=24)
signed = [float(x["rate"]) * (1 if x["direction"] == "long" else -1) for x in f["fundings"]]
print("24h funding paid by longs: %.4f%%" % sum(signed))

# 2) public trades, newest first, paged with next_cursor (no key needed)
cursor, trades = None, []
for _ in range(3):                           # 3 pages x 100; each call weighs 200
    params = dict(market_id=MARKET, sort_by="timestamp", limit=100)
    if cursor:
        params["cursor"] = cursor
    page = get("trades", **params)
    trades += page["trades"]
    cursor = page.get("next_cursor")
    if not cursor:
        break
    time.sleep(1.1)                          # stay under 60 requests/minute on Standard
liq = [t for t in trades if t["type"] == "liquidation"]
print(len(trades), "trades,", len(liq), "liquidations, $%.0f notional" % sum(float(t["usd_amount"]) for t in trades))

# 3) hourly candles: at most 500 per call; keys t o h l c v V
c = get("candles", market_id=MARKET, resolution="1h",
        start_timestamp=now_ms - 48 * 3600 * 1000, end_timestamp=now_ms, count_back=48)
print("last close", c["c"][-1]["c"], "24h quote volume $%.0f" % sum(x["V"] for x in c["c"][-24:]))

For live data, subscribe to market_stats/all once instead of polling every market: it carries mark and index price, open interest, the funding estimate and 24h volume for all markets on one subscription. Cross-venue funding work starts with funding-rates; the maths is in our funding rates guide.

Historical data and bulk downloads

  • Your own account: export returns CSVs with up to 12 months of trades or 3 months of funding payments for one account index (auth needed); deposit_history, withdraw_history, transfer_history, pnl and positionFunding cover the rest.
  • Every trade on the exchange: export/historicalTrades?l1_address=…&date=YYYY-MM-DD returns a one-hour download link to that day’s parquet file, back to mainnet genesis (January 17, 2025) and updated daily at 8 PM UTC. Access needs a one-time in-app transfer of 100 LIT to the address in the docs; the same files sit in a requester-pays S3 bucket (s3://lighter-historical-data/trade-daily/) for AWS users.
  • Order-book history and replay: Lighter does not publish it. Its docs list two paid providers: 0xArchive (order books including order-level data, trades, candles, funding and open interest since January 2025) and Tardis.dev (tick-level data since April 17, 2026).
  • Batches, blocks and logs: the separate explorer API, explained in our Lighter explorer guide.

8. Rate limits by account type

LimitStandardPlusPremiumBuilder
REST requests / rolling minute60 (not weighted)24,000 weighted24,000 weighted240,000 weighted (reads)
sendTx + sendTxBatch / minuteinside the 604,0004,000 at 0 LIT → 72,000 at 500,000 LITStandard limits
Pending orders (per account / market)50 / 10500 / 1001,000 / 100–
Active orders (per account / market)250 / 30750 / 2501,500 / 1,000–

Source: apidocs.lighter.xyz Rate Limits (updated October 13, 2026; read October 15, 2026). Premium sendTx tiers by staked LIT: 0 → 4,000; 1,000 → 5,000; 3,000 → 6,000; 10,000 → 7,000; 30,000 → 8,000; 100,000 → 12,000; 300,000 → 24,000; 500,000 → 72,000. Fee credits count as staked LIT. Plus gets 4,000 sendTx and 24,000 weighted reads a minute; the help-center fee page and the API docs now list the same figures. Builder accounts are free on application via Discord.

Lighter API documentation Rate Limits page
Lighter’s official API docs, Rate Limits page (September 25, 2026) — the source for the per-account-type limits in this section.

Weights (Premium/Plus): sendTx, sendTxBatch and nextNonce cost 6; publicPools and txFromL1TxHash 50; order-history endpoints 100; exchangeMetrics 120; apikeys 150; trades 200; transferFeeInfo 500; recentTrades 600; changeAccountTier, tokens/* and referral/* 3,000; tokens/create 23,000; most others 300. When a weighted limit divided by an endpoint’s weight is smaller than 60, that lower number applies to Standard accounts too (8 calls a minute for changeAccountTier). WebSocket, per IP: 255 connections, 255 new connections per minute, 500 subscriptions per connection and 5,000 per IP, 500 unique accounts per connection, 200 client messages per minute and 50 in-flight messages (transactions excluded). Per transaction type (all accounts): 40 per minute by default; withdrawals 2 per minute and 3 per 2 hours (shared by every sub-account of one address); sub-account and public-pool creation 2 per minute; leverage updates 40 per minute; transfers 120 per minute; key changes 300 per minute; fast withdrawals 10 per hour. A withdrawal sent directly to the Ethereum contract is an L1 transaction and not API-limited.

How the account types differ beyond the API (fees, latency, sub-accounts and when Premium pays off) is covered in our Lighter fees guide: account types and rate limits. The block-explorer API has its own budget of 90 weighted requests a minute for every account type.

Volume Quota (Premium): create, modify, cancel-all and grouped orders consume quota; single cancels do not. You start with 1,000, earn 1 more for every $2 traded, can bank up to 15,000,000, and get one free sendTx every 15 seconds. Quota is shared by all sub-accounts under one L1 address. Example: a bot that trades $1,000,000 of notional earns 500,000 extra order transactions.

What happens when you exceed a limit: HTTP 429 or 405, WebSocket disconnects for message floods, and REST and WebSocket limits are linked. Cooldown is 60 seconds if the firewall blocked you; if the API servers did, it is the endpoint weight ÷ (your per-minute weight ÷ 60), for example 300 ÷ (24,000 ÷ 60) = 0.75 s for the account endpoint. Authenticating every request moves you from IP-based to L1-address-based limits.

9. Standard vs Premium vs Plus for API traders

AccountMaker / takerTaker latencyMaker / cancelOrder tx per minuteFits
Standard (default)0% / 0%300 msMaker not delayed; cancel 300 ms60 requests in totalDCA, slow grids, hourly funding bots
Plus (opt-in)0.005% / 0.005%300 ms200 ms maker and cancel4,000Many markets, not latency-sensitive
Premium (opt-in)0.0040% / 0.0280% at 0 LIT; 0.0028% / 0.0196% at 500,000 LIT140 msNo added delay on post-only orders and cancels4,000–72,000Market making, arbitrage, HFT

Sources: apidocs Account Types (updated October 4, 2026) and docs.lighter.xyz Trading Fees, both read October 6, 2026, when they agreed on every latency in this table; order-transaction limits from Rate Limits (read October 15, 2026). The fee pages do not cover Advanced orders; when we checked the app in September 2026 its order ticket showed a 0.01% fee line for TWAP even on Standard, so check the ticket before you rely on 0% for advanced order types (see our order types guide). Switch tiers with POST /api/v1/changeAccountTier: you can upgrade at any time, but you have to wait 24 hours after the last change before calling it again. Sub-accounts share the main address's tier.

Worked example. A bot that takes $1,000,000 of notional a month pays $0 on Standard, $50 on Plus (0.005%) and $280 on Premium (0.0280%) at 0 staked LIT. A market maker that only rests post-only orders pays $40 per $1,000,000 on Premium (0.0040%). Standard is not a trap: if your strategy sends fewer than one request per second and a 300 ms delay does not matter, it is the cheapest venue there is. It stops working when you need to requote many times a second, because 60 requests a minute also covers your reads. For the complete fee picture see our Lighter fees guide; for the same comparison on Hyperliquid, where API keys are "agent wallets", see our Hyperliquid API guide.

10. Error handling

CodeMessageUsual fix
21104 / 21105invalid nonce / batch nonce not increasingRe-fetch nextNonce for that key; never share one key between processes.
21108 / 21136invalid PublicKey, run changePubKey / update the SDKRegister the key (Step 4) or upgrade lighter-sdk.
21109 / 21110api key not found / invalid api key indexCheck the index belongs to this account index (keys are per sub-account).
21120invalid signatureWrong private key for the index, or wrong chain ID (mainnet 304, testnet 300).
21506too many pending txsBack off and retry.
21507 / 21508 / 21739below maintenance / initial margin; not enough margin for the orderReduce size or add collateral.
21702 / 21711invalid price / invalid expiryScale by price_decimals; expiry 5 min to 30 days.
21733 / 21734price flagged as accidental / too far from markThe fat-finger check; keep limits near the mark price.
21732reduce only increases positionWrong side or size on a reduce-only order.
23000–23003Too Many Requests / Subscriptions / Accounts / ConnectionsYou hit a REST or WebSocket limit; see section 8.

From apidocs.lighter.xyz "Data Structures, Constants and Errors". Transaction status: 0 failed, 1 pending, 2 executed, 3 pending-final.

Three habits prevent most incidents: treat code=200 as "accepted", not "filled", and confirm fills on the account channels; retry with exponential backoff on 429/405; and use a dead man's switch: cancel_all_orders has a scheduled mode (CANCEL_ALL_TIF_SCHEDULED plus a timestamp) and an abort mode, so a bot can keep pushing the deadline forward and have its orders cancelled if it stops. As a censorship-resistant fallback, Lighter's Ethereum contract (0x3B4D794a66304F130a4Db8F2551B0070dfCf5ca7) accepts "priority transactions" such as cancel-all, reduce-only IOC orders and withdrawals directly from L1.

11. Security checklist

  • The Ethereum key never touches the bot server. You only need it to register keys, create sub-accounts and send fast withdrawals or transfers.
  • One API key per process and ideally per order type: separate nonces, separate blast radius, and a leaked key can be replaced without touching the others.
  • Run bots in sub-accounts so a bug can only lose the margin you gave that bot.
  • Read-only tokens for dashboards, never a signing key.
  • Short auth tokens: create a new one well before the 8-hour maximum.
  • Keep keys out of code and git: environment variables or a secret manager.
  • Watch the API channel for breaking changes; SDK updates sometimes change the key format (error 21136).

More ideas for what to run once the plumbing works: trading bots on Lighter (below), funding-rate and cross-venue arbitrage. Comparing venues first? Rate limits, SDKs and latency sit side by side in perp DEX APIs compared, and the wider picture is in the best DEX guide.

12. Trading bots on Lighter: a post-only grid, bot patterns and third-party bots

Lighter has no built-in grid bot, so “a Lighter trading bot” means a program that signs orders with your API key, usually on the Python SDK above, or a third-party platform that does it for you. If all you need is to work a large order slowly, try the app's native order tools first (TWAP sends a slice every 30 seconds for 5 minutes to 30 days, plus Scale, Chase, Iceberg and Atomic; see Lighter order types); TWAP, Chase, Iceberg and RFQ cost 0.01% on a Standard account.

Code skeleton: a post-only grid

The skeleton lays a small post-only grid on ETH (market index 0). Prices and sizes are integers: on September 25, 2026 the ETH market had 2 price decimals and 4 size decimals, so $2,600.00 is 260000 and 0.0100 ETH is 100. Read the decimals from orderBookDetails at start-up rather than hard-coding them.

import asyncio, os
import lighter

BASE_URL = "https://mainnet.zklighter.elliot.ai"
ACCOUNT_INDEX = int(os.environ["LIGHTER_ACCOUNT_INDEX"])   # the bot's sub-account
API_KEY_INDEX = int(os.environ["LIGHTER_API_KEY_INDEX"])   # 4 or higher
PRIVATE_KEY = os.environ["LIGHTER_API_PRIVATE_KEY"]        # never hard-code keys

MARKET = 0                 # ETH perp
LOW, HIGH, LEVELS = 260000, 278000, 10   # $2,600.00 to $2,780.00
SIZE = 100                 # 0.0100 ETH per order (min 0.002 ETH and $10)

async def main():
    client = lighter.SignerClient(
        url=BASE_URL,
        api_private_keys={API_KEY_INDEX: PRIVATE_KEY},
        account_index=ACCOUNT_INDEX,
    )
    mid = 268900           # replace with the live mid from the order_book channel
    step = (HIGH - LOW) // LEVELS
    for i in range(LEVELS + 1):
        price = LOW + i * step
        if abs(price - mid) < step // 2:
            continue       # no order right at the mid
        tx, tx_hash, err = await client.create_order(
            market_index=MARKET,
            client_order_index=1000 + i,          # your own id, used to cancel later
            base_amount=SIZE,
            price=price,
            is_ask=price > mid,                   # sells above mid, buys below
            order_type=client.ORDER_TYPE_LIMIT,
            time_in_force=client.ORDER_TIME_IN_FORCE_POST_ONLY,
            reduce_only=False,
            order_expiry=client.DEFAULT_28_DAY_ORDER_EXPIRY,
        )
        if err is not None:
            print("order failed", i, err)
    await client.close()

asyncio.run(main())

What a real bot adds: a listener on account_all that re-places the opposite order one step away after each fill; a position cap; the reconnect and keepalive logic from section 6; a scheduled cancel-all as a dead man's switch and reduce-only exits (section 10); and one sub-account per strategy (4 on Standard, 16 on Plus, 64 on Premium), so a runaway bot can only lose that balance.

Bot patterns that fit Lighter

  • Grid. Fits Standard if the grid is wide and slow: every rung is a free post-only order and a fill-then-replace cycle costs a couple of requests. Watch the per-market caps (10 pending and 30 active orders on Standard) and the $10 minimum per rung. In a trend a long-only grid keeps buying, so cap total size and set a stop below the range.
  • TWAP and execution. The native TWAP runs on the exchange for 0.01% on Standard; a self-sliced loop of plain orders is free but each slice spends one of the 60 requests a minute and waits 300 ms.
  • Market making. Standard cancels wait 300 ms, so stale quotes get picked off in a fast move. Premium removes the delay on post-only orders and cancels, charges 0.004% maker and allows maker-only API keys. Set the self-trade prevention mode deliberately (the default since May 31, 2026 is cancel maker).
  • Funding and basis. Funding is paid hourly (capped at 0.5% an hour); a basis bot can post ETH through multi-asset margin and short the ETH perp while delta-neutral. Funding changes sign, so the bot needs an exit rule (Lighter funding in detail).

What a bot really pays

ScenarioStandardPremium (0 LIT)
Grid: one buy + one sell of 0.01 ETH at about $2,690 (both maker)$02 × $26.90 × 0.004% ≈ $0.002
$1M of monthly taker volume (scalping bot)$0$280
$1M of monthly maker volume (market maker)$0$40
Native TWAP of $100,000$10 (0.01%)Premium maker/taker rates on the slices

Fee rates from docs.lighter.xyz via our facts registry; ETH price from the Lighter API on September 25, 2026. Funding, slippage and adverse selection are not included, and for a fast bot they are usually larger than the fee.

Fees rarely decide whether a Lighter bot works: they are zero on Standard and small on Premium next to what speed saves a market maker. Latency and the request budget are the real costs. The same $1M of taker volume costs $450 at Hyperliquid's base 0.045% and $400 at Aster's 0.04%.

Third-party Lighter bots

One service we could check against its own documentation is Dextrabot: its docs describe a Lighter grid bot that trades through a Lighter API wallet you add, needs a beta access code and a funded USDC balance, and offers 2 to 36 grids (arithmetic or geometric) in cross margin only. We have not tested it with money and do not endorse any provider. Before connecting any service, check:

  • How it charges. Since September 14, 2026 Lighter rejects Partner Attribution trades from Standard accounts: platforms that add an integrator fee (up to 10 bps on perps) need your account on Plus or Premium, and those trades are excluded from Lighter's referral rebates.
  • What the key can do. A Lighter API key can trade and request secure withdrawals to your own address only. Never give a service your wallet seed phrase.
  • Where it runs. A dedicated sub-account holding only the capital the bot needs.

Other venues: Aster has native grid trading, and Hyperliquid has the widest third-party bot ecosystem (best DEX trading bots, Hyperliquid grid bots).

Run your bot in its own Lighter sub-account

Regular orders are free on Standard. Referral code 718610TD: if your bot needs Premium, your first week of Premium fees is rebated.

Open Lighter →

FAQ

Does Lighter have an API?

Yes. Lighter has a public REST API at https://mainnet.zklighter.elliot.ai/api/v1/, a WebSocket API at wss://mainnet.zklighter.elliot.ai/stream, an official Python SDK (pip install lighter-sdk), a Go SDK (lighter-go) and an open-source TypeScript client demo (lighter-ts). Market data is public; placing orders requires an API key registered to your account.

Do I need an API key for Lighter market data?

No. Trades, order books, candles, funding rates and exchange stats are public: call https://mainnet.zklighter.elliot.ai/api/v1/ endpoints such as recentTrades, trades, orderBookOrders, candles and fundings, or subscribe to the order_book, trade, candle and market_stats WebSocket channels, without any key. A key or auth token is only needed for your own orders and for account-level history such as liquidations and exports. On a free Standard account the 60-requests-a-minute limit still applies, so stream with the WebSocket instead of polling.

How do I get historical Lighter data?

Candles (up to 500 per call) and hourly or daily funding (up to 750 per call) go back through the public REST API with start and end timestamps. Your own trades and funding payments export as CSV (up to 12 months of trades). Every trade on the exchange since mainnet genesis on January 17, 2025 is available as daily parquet files through export/historicalTrades after a one-time in-app transfer of 100 LIT, or from a requester-pays S3 bucket. For order-book history, Lighter’s docs point to third-party providers such as 0xArchive and Tardis.dev.

How do I get a Lighter API key?

Generate a keypair with the SDK (lighter.create_api_key() or the system_setup.py example), then bind it to your account index with one signature from the Ethereum wallet that owns the account. The official Get Started page (updated September 24, 2026) says indices 0–1 are reserved for the web and mobile apps and you can create up to 253 keys at indices 2–254; the older API-keys page still lists 0–3 as reserved, so index 4 or higher is safe under both.

What is the account index on Lighter?

Lighter identifies accounts by an integer, not by your wallet address. One Ethereum address can own a main account and several sub-accounts, each with its own index. Look them up with AccountApi.accounts_by_l1_address(l1_address=...) and pass the right index to the SignerClient.

What are the Lighter API rate limits?

Standard (free) accounts get 60 REST requests per rolling minute, sendTx included. Premium and Plus accounts get 24,000 weighted requests per minute plus a separate sendTx/sendTxBatch budget: 4,000 per minute at 0 staked LIT, rising to 72,000 at 500,000 LIT on Premium. WebSocket limits per IP: 255 connections, 500 subscriptions per connection, 5,000 per IP, 200 client messages per minute (apidocs Rate Limits, updated October 13, 2026; read October 15, 2026). Account-type differences are summarised in our Lighter fees guide.

Can a Lighter API key withdraw my funds?

Only to your own wallet. An API key can sign secure withdrawals, which can only go to the L1 address that created the account. Fast withdrawals and transfers to other addresses also need the Ethereum wallet’s signature. For dashboards, use a read-only token, which cannot sign any transaction.

How do I use the Lighter WebSocket API?

Connect to wss://mainnet.zklighter.elliot.ai/stream and send a message with type subscribe and a channel such as order_book/1 (BTC), trade/1, market_stats/all or account_all/YOUR_ACCOUNT_INDEX. Private channels like account_orders need an auth token in the auth field. Send a frame at least every 2 minutes (a JSON ping gets a pong), check that each order-book update’s begin_nonce matches the previous nonce, and resubscribe after any reconnect.

Is the Lighter API free to use?

The API itself is free. Trading fees depend on the account type: Standard is 0% maker / 0% taker with a 300 ms taker delay, Premium is 0.0040% maker / 0.0280% taker at 0 staked LIT with 140 ms taker latency, and Plus is 0.005% on both sides with higher rate limits than Standard.

Is there a Lighter testnet?

Yes. Use https://testnet.zklighter.elliot.ai as the base URL (chain ID 300) and wss://testnet.zklighter.elliot.ai/stream for WebSocket. The Python SignerClient picks the chain ID from the URL automatically.

Why did my Lighter order return code 200 but never fill?

A 200 response only means the API server accepted a correctly formatted transaction. The sequencer can still reject it, for example a taker order whose worst acceptable price cannot be met is cancelled. Watch the account_orders or account_all WebSocket channel to confirm what actually happened. For taker orders the nonce still increases in that case.

Does Lighter have a built-in grid bot?

No, not as of October 2026. The app has TWAP, Scale, Chase, Iceberg and Atomic orders but no grid bot. You can build one with the SDK (post-only limit orders at fixed price steps, as in the skeleton on this page) or use a third-party tool that trades through a Lighter API key you add. Regular orders from a bot are free on a Standard account; the 60-requests-a-minute limit is the real constraint.

Can third-party bots trade on a Lighter Standard account?

Not through Lighter’s partner program. Since September 14, 2026, trades routed through Partner Attribution from Standard accounts are rejected, so platforms that charge an integrator fee (up to 10 bps on perps) need your account on Plus or Premium. A tool that simply uses your own API key without partner attribution is not affected; check which model a provider uses before you connect it.

Build on Lighter

Register with code 718610TD, fund a sub-account for the bot, and trade at 0% on Standard. If you move to Premium, your first week of Premium fees is rebated.

Sign up for Lighter →

Want the deepest perp order book as your second venue? Open Hyperliquid with code HOLYGRAIL for 4% off fees on your first $25M of volume. Its API uses agent wallets; see the Hyperliquid WebSocket guide.

Disclosure: 718610TD is our Lighter referral code; we earn a share of fees paid by referred accounts (Premium fees, and advanced-order fees on Standard; plain Standard orders are free). Endpoints, limits, fees and error codes were checked in Lighter’s API docs, SDK repository and public API on October 15, 2026. Limits change, so check apidocs.lighter.xyz before you deploy. Nothing here is financial advice.