Lighter API (2026): Docs, Python SDK, API Keys, WebSocket and Rate Limits
Short answer. The Lighter API is a signed-transaction API documented at apidocs.lighter.xyz, with official Python (lighter-sdk 1.1.8), Go and TypeScript SDKs. Market data is public; trading needs an API key (index 4–254) on a funded account.
API keys are created from a funded Lighter account, so open it first. Sign up with code 718610TD: 10% of your trading fees rebated every Monday on Premium or paid advanced orders.
On Lighter, a referral code can only be attached at sign-up and cannot be added later. Plain Standard orders are free. We earn a commission on referred accounts' fees; it never raises yours.
REST: https://mainnet.zklighter.elliot.ai/api/v1/ · WebSocket: wss://mainnet.zklighter.elliot.ai/stream · Rate limits: 60 requests a minute on the free Standard account, 24,000 weighted on Premium and Plus.
By Cipher Intelligence Editorial Team · Published · Updated · 14 min read · Docs, SDKs and the app's API-key page re-read and the public REST and WebSocket APIs tested on October 22, 2026 · How we verify
What changed on this page (1)
— Reorganised as the Lighter API reference: where the docs are, SDKs by language, creating an API key in the app (indexes 4 to 254, 251 keys), every rate limit and endpoint weight from the current API docs, updated code examples at October 22 prices, and the current partner-fee rule for bots.
Lighter API docs and endpoints
Lighter is a zk-rollup perpetuals exchange: orders are matched by a sequencer and settled with proofs on Ethereum. That shapes the API. There is no "API secret" for an HMAC function. Every account (and every sub-account) has numbered API key slots; each slot holds a public key registered on the exchange, and you keep the private key and use it to sign transactions such as create, modify and cancel order. Reads are ordinary REST calls; some of them, and the private WebSocket channels, need a short-lived auth token derived from the same key. Keys sign transactions; tokens authenticate reads.
Chain ID 300. Test signing, nonces and precision here first
Explorer API
https://explorer.elliot.ai/api/
Batches, blocks, logs, accounts; its own limit of 90 weighted requests a minute (Lighter explorer guide)
Status and announcements
status.lighter.xyz · Telegram API channel · Discord #api-updates
Upgrades and breaking changes
Read October 22, 2026. For the lowest latency the docs recommend AWS Tokyo ap-northeast-1, availability zone ID apne1-az4; Premium accounts on the top staking tier can request direct access that skips CloudFront.
Lighter SDKs: Python, Go, TypeScript
SDK
Install / repo
What it is
Python SDK
pip install lighter-sdk (1.1.8 on PyPI) · github.com/elliottech/lighter-python
Official, Apache-2.0, Python 3.8+. REST API classes (AccountApi, OrderApi, TransactionApi…), SignerClient with bundled signer binaries for macOS, Linux and Windows, a simple WsClient, and dozens of example scripts.
Go SDK
github.com/elliottech/lighter-go
Official. Signing code, transaction types and constants; the source of the Python signer and of the shared library that Rust, Java and C++ examples call.
TypeScript / JavaScript
lighter-go wasm/ (WebAssembly signer)
The official TypeScript route: the Go signer compiled to WebAssembly (quickstart needs Node.js 22+). github.com/elliottech/lighter-ts is a reference client (state store, WebSocket pipeline), not a signer.
AI agents
github.com/elliottech/lighter-agent-kit
Official MIT kit that lets coding agents read data and trade through a Lighter API key; the docs also publish llms.txt and .md pages.
There is no official Rust SDK; lighter-go's examples show Rust, Java and C++ calling its shared library. Unofficial SDKs (C#, Rust, TypeScript packages) and connectors such as Hummingbot's exist; read their defaults before trading, because some attach a partner fee to every order, which only works on Plus or Premium accounts (bots and partner fees).
Lighter API key: create one in the app or the SDK
Account index
Lighter addresses accounts by an integer account index, not by your wallet address. One Ethereum (L1) address can own a main account and sub-accounts, each with its own index, margin, positions and key set: 4 sub-accounts on Standard, 16 on Plus, 64 on Premium. Look them up with AccountApi.accounts_by_l1_address. Only accounts that have deposited get an index.
Create an API key in the app
Open app.lighter.xyz, connect the wallet that owns the account and go to Tools → API Keys (app.lighter.xyz/apikeys).
Choose Generate API Key and pick an index from 4–254. Indexes 0–3 are reserved for Lighter's web and mobile apps, so you can create up to 251 keys per account.
Sign with your wallet. The app shows the private key once: "It will no longer be accessible after closing this popup." Store it in your bot's secret store.
app.lighter.xyz/apikeys on October 22, 2026, logged out: "You can generate keys if you have an account." Connect the account's wallet to generate one.
Or create it with the SDK
Run examples/system_setup.py from the lighter-python repo; its default API_KEY_INDEX is 3, a reserved slot, 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, and checks the result with check_client(). Run it on a trusted machine and keep the Ethereum key off the bot server. Index 255 is a special value that returns all keys when you query apikeys. Every key has its own nonce, so give each bot process its own key.
Permissions, auth tokens and read-only tokens
An API key can read auth-gated data, trade, and sign secure withdrawals, which only go to the L1 address that created the account. Fast withdrawals and transfers to other addresses also need the Ethereum wallet's signature.
create_auth_token_with_expiry() produces a token for authenticated REST calls and private WebSocket channels: default lifetime 10 minutes, maximum 8 hours.
A read-only token (1 day to 10 years, from the tokens/create endpoint or app.lighter.xyz/read-only-tokens) reads the same data but cannot sign anything: the right credential for dashboards and AI agents.
Maker-only keys (Premium): up to 251 keys per account index can be limited to post-only orders, post-only modifies and cancels, and in return skip the speed bump. Changes have a one-hour cooldown.
Step 2: install the SDK and find your account index
pip install lighter-sdk
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 3: register an API key
In the app (above) or with system_setup.py, at index 4 or higher.
Step 4: read the market's precision
Prices and sizes are integers. GET /api/v1/orderBookDetails tells you how many decimals each market uses. On October 22, 2026 the ETH perp (market 0) had size_decimals 4, price_decimals 2, a minimum of 0.002 ETH and $10, and last traded at $2,442.28. So 0.0050 ETH is base_amount=50 and $2,400.00 is price=240000.
Step 5: 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"]) # 4 or higher
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=50, # 0.0050 ETH (min 0.0020 ETH and $10)
price=240000, # $2,400.00: below the market, so it rests
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())
A post-only buy priced above the best ask is cancelled instead of resting, so read the book before you pick a price. 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. Run the script against testnet first, then send one minimum-size mainnet order and confirm it under your account index before you let a loop run.
Signing, nonces, order types and batches
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); TransactionApi.next_nonce returns the next one.
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.
Order types: limit (0), market (1), stop-loss (2), stop-loss limit (3), take-profit (4), take-profit limit (5), TWAP (6). 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) protects against out-of-order modifies.
Batches: all transactions in a batch must use the same account and API key. The trading page (updated October 19, 2026) allows up to 50 per batch but calls REST batches of about 15 the safe size; the WebSocket reference still says 15 per message. Stay at 15 until the two pages agree.
Lighter WebSocket API: channels, auth, keepalive and a Python client
Use REST for setup and occasional reads 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 one socket.
Connect:wss://mainnet.zklighter.elliot.ai/stream (append ?readonly=true to read public data from a restricted region). The server answers {"session_id": "…", "type": "connected"}.
Subscribe with {"type": "subscribe", "channel": "order_book/1"}; private channels add "auth": "<token>". Replies use a colon ("channel": "order_book:1") and a type prefix: subscribed/order_book (the snapshot), then update/order_book.
Keepalive: send at least one frame every 2 minutes; {"type":"ping"} is answered with {"type":"pong"}.
Limits per IP: 255 connections and 255 new connections a minute, 5,000 subscriptions and 5,000 unique accounts in total (500 per connection), 200 client messages a minute and 50 in-flight messages (transactions excluded). Going over returns errors 23000–23003.
Channel (subscribe as)
What you get
Auth token?
order_book/MARKET
Full snapshot, then changed price levels every 50 ms, with begin_nonce and nonce for continuity checks
No
ticker/MARKET
Best bid and offer
No
trade/MARKET
Public trades, with liquidation trades in a separate liquidation_trades array
No
market_stats/MARKET or /all
Index, mark and mid price, open interest, next-funding estimate and last funding, 24h volume and range
Your orders with status such as filled or canceled-post-only
Yes
account_tx/ACCOUNT · notification/ACCOUNT
Your transactions; liquidation, deleverage and announcement notifications
Yes
From the WebSocket reference (updated October 18, 2026). Channels marked "No" are readable for any account index, so treat your positions as public. In a 15-second test on the BTC book on October 22, 2026 we received the snapshot and 279 updates (about 18 a second), and every begin_nonce check passed.
A reconnecting Python client
The SDK's WsClient handles only order_book and account_all; since September 30, 2026 it logs other message types instead of raising, but it still does not reconnect, ping or check nonces. This client keeps a local book with the nonce check, pings every minute and reconnects with capped backoff:
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, 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
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"])
finally:
ka.cancel()
except Exception as exc:
print("reconnecting:", exc)
await asyncio.sleep(min(backoff, 30) + random.random())
backoff *= 2
asyncio.run(stream())
Signed transactions can go over the same socket as jsonapi/sendtx or jsonapi/sendtxbatch; they count against the same transaction limits as REST. As with REST, an accepted transaction is not a fill: confirm on account_orders or account_all. After any reconnect, resubscribe and rebuild from the new snapshot, and treat REST (account, orders) as the source of truth.
Lighter market data API: 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/; markets are integers (0 is ETH, 1 is BTC; orderBookDetails lists all of them with their decimals). 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.
Data
REST (GET, public)
WebSocket
Notes
Markets and precision
orderBookDetails · orderBooks?filter=perp
–
size and price decimals, minimums, status, fees; cache at start-up
Order book (price levels)
–
order_book/MARKET · ticker/MARKET
snapshot, then changed levels; size 0 removes a level
Order book (order by order)
orderBookOrders?market_id=1&limit=250
–
each resting order with its owner account index, size, price and expiry; limit up to 250
Recent trades
recentTrades?market_id=1&limit=100
trade/MARKET
limit up to 100; weight 600
Trade history
trades?market_id=1&sort_by=timestamp&limit=100
–
paged with next_cursor; filters type (trade, liquidation, deleverage, market-settlement), role, from; weight 200
Candles
candles · markPriceCandles
candle/MARKET/1m
resolution 1m to 1d, start_timestamp, end_timestamp, count_back; at most 500 per call
Funding
fundings · funding-rates
market_stats/MARKET
fundings: 1h or 1d, up to 750 per call; funding-rates: a snapshot across Lighter, Binance, Bybit and Hyperliquid
Liquidations (market-wide)
trades?…&type=liquidation
trade/MARKET → liquidation_trades
public; see the paging note below
Liquidations (your account)
liquidations?account_index=…
notification/ACCOUNT
needs an auth token (code 20001 without one)
Mark, index, OI, 24h volume
orderBookDetails?market_id=1 · exchangeStats
market_stats/all
the next-funding estimate is on the WebSocket
Market data and Historical Data guides and the API reference; every REST call above re-run on October 22, 2026. Weights apply to Premium and Plus (other endpoints weigh 300).
Funding:rate is a percentage for one hourly payment and direction says who pays (long means longs pay shorts). fundings timestamps are in seconds; candles and trades use milliseconds.
Candles use one-letter keys: t open time, o h l c, v base volume, V quote volume. The last candle is the one still open.
Trades include usd_amount, both order and account ids, is_maker_ask and the fees each side paid, as integers in millionths of notional (28 = 0.0028%).
Liquidations: on October 22, 2026, trades?market_id=1&type=liquidation returned the latest few liquidation fills with no next_cursor, while the same filter without market_id returned 100 fills across markets with a cursor. For a complete feed, record the liquidation_trades array of the trade channel.
History: your own trades and funding export as CSV (up to 12 months of trades); every trade on the exchange since mainnet genesis is available as daily parquet files through export/historicalTrades after a one-time transfer of 100 LIT; order-book history comes from third parties such as 0xArchive and Tardis.dev.
Example: funding, trades and candles in Python (no key)
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
cursor, trades = None, []
for _ in range(3):
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
print(len(trades), "trades")
# 3) hourly candles: 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"])
For live data, subscribe to market_stats/all once instead of polling every market. Cross-venue funding work starts with funding-rates; the maths is in our funding rates guide.
Lighter API rate limits
Limit
Standard
Plus
Premium
Builder
REST requests per rolling minute
60 (not weighted)
24,000 weighted
24,000 weighted
240,000 weighted (reads)
sendTx + sendTxBatch per minute
inside the 60
4,000
4,000 at 0 LIT → 72,000 at 500,000 LIT
Standard limits
Pending orders (per account / market)
50 / 10
500 / 100
1,000 / 100
–
Active orders (per account / market)
250 / 30
750 / 250
1,500 / 1,000
–
Source: apidocs.lighter.xyz Rate Limits, read October 22, 2026. Premium sendTx per minute 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. Builder accounts are free on application via Discord.
Lighter's API docs, Rate Limits page, on October 22, 2026: Builder 240,000, Plus and Premium/Advanced 24,000 weighted requests and Standard 60 requests per rolling minute, followed by the endpoint weights.
Weights (Premium/Plus): sendTx, sendTxBatch and nextNonce 6; publicPools and txFromL1TxHash 50; order-history endpoints 100; exchangeMetrics 120; apikeys 150; trades 200; transferFeeInfo 500; recentTrades 600; changeAccountTier, tokens/*, referral/* and a few account endpoints 3,000; tokens/create 23,000; most others 300. changeAccountTier is capped at 8 calls a minute on every account type.
Per transaction type (all accounts): 40 a minute by default; withdrawals 2 a minute and 3 per 2 hours per L1 address; transfers 120 a minute; key changes 300 a minute; fast withdrawals 10 an hour. A withdrawal sent directly to the Ethereum contract is not API-limited.
Volume Quota (Plus and 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; sendTx responses now report volume_quota_remaining.
When you exceed a limit: HTTP 429 or 405, and WebSocket disconnects for message floods. Cooldown is 60 seconds if the firewall blocked you; otherwise it is the endpoint weight ÷ (your per-minute weight ÷ 60). Authenticating every request moves you from IP-based to L1-address-based limits.
How the account types differ beyond the API (fees, latency, sub-accounts and when Premium pays off) is in our Lighter fees guide.
Standard vs Premium vs Plus for API traders
Account
Maker / taker
Taker latency
Order tx per minute
Fits
Standard (default)
0% / 0%
300 ms (cancels 300 ms)
60 requests in total
DCA, slow grids, hourly funding bots
Plus (opt-in)
0.005% / 0.005%
300 ms
4,000
Many markets, not latency-sensitive
Premium (opt-in)
0.004% / 0.028% at 0 LIT
140 ms; no added delay on post-only orders and cancels
4,000–72,000
Market making, arbitrage, HFT
Lighter's fee page and the API docs' Account Types page, read October 22, 2026. They disagree on Plus maker and cancel latency (the fee page says 200 ms for both; the Account Types page says 0 ms maker and 300 ms cancel), so measure it yourself if it matters. TWAP orders cost 0.01% on Standard accounts. Switch tiers with POST /api/v1/changeAccountTier; upgrades have no cooldown, other changes need 24 hours since the last call.
Worked example. A bot that takes $1,000,000 of notional a month pays $0 on Standard, $50 on Plus and $280 on Premium at 0 staked LIT; a post-only market maker pays $40 per $1,000,000 on Premium. Standard stops working when you need to requote many times a second, because 60 requests a minute also covers your reads. The same comparison on Hyperliquid, where API keys are "agent wallets", is in our Hyperliquid API guide.
Lighter API error codes
Code
Message
Usual fix
20001
invalid param (e.g. auth missing)
Add the auth token or fix the query parameters.
21104 / 21105
invalid nonce / batch nonce not increasing
Re-fetch nextNonce for that key; never share one key between processes.
21108 / 21136
invalid PublicKey, run changePubKey / update the SDK
Register the key or upgrade lighter-sdk.
21109 / 21110
api key not found / invalid api key index
Check the index belongs to this account index (keys are per sub-account).
21120
invalid signature
Wrong private key for the index, or wrong chain ID.
21159
partner fee with a non-default self-trade mode
Use the default self-trade mode on orders that carry an integrator fee.
21506
too many pending txs
Back off and retry.
21507 / 21508 / 21739
below maintenance / initial margin; not enough margin
Reduce size or add collateral.
21702 / 21711
invalid price / invalid expiry
Scale by price_decimals; expiry 5 min to 30 days.
21733 / 21734
price flagged as accidental / too far from mark
The fat-finger check; keep limits near the mark price.
23000–23003
Too Many Requests / Subscriptions / Accounts / Connections
You hit a REST or WebSocket limit (above).
Three habits prevent most incidents: treat code=200 as "accepted", not "filled"; retry with exponential backoff on 429/405; and use a dead man's switch: cancel_all_orders has a scheduled 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 accepts "priority transactions" such as cancel-all, reduce-only IOC orders and withdrawals directly from L1.
Security checklist
The Ethereum key never touches the bot server. You need it only to register keys, create sub-accounts and send fast withdrawals or transfers.
One API key per process: 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 and AI agents, never a signing key.
Short auth tokens: create a new one well before the 8 hours maximum.
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).
Trading bots on Lighter: a post-only grid 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, 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 TWAP first (one slice every 30 seconds for 5 minutes to 30 days; Lighter order types); it costs 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) around the October 22, 2026 price of about $2,442.28. Read the decimals and the live mid from the API 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 = 235000, 253000, 10 # $2,350.00 to $2,530.00
SIZE = 100 # 0.0100 ETH per order (min 0.002 ETH and $10)
async def main():
client = lighter.SignerClient(
url=BASE_URL,
account_index=ACCOUNT_INDEX,
api_private_keys={API_KEY_INDEX: PRIVATE_KEY},
)
mid = 244200 # 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())
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 logic above, a scheduled cancel-all as a dead man's switch and one sub-account per strategy. On Standard, watch the per-market caps (10 pending and 30 active orders) and the $10 minimum per rung; cancels wait 300 ms, so stale quotes get picked off in a fast move.
What a bot really pays
Scenario
Standard
Premium (0 LIT)
Grid: one buy + one sell of 0.01 ETH at about $2,442.28 (both maker)
$0
2 × $24.42 × 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 Lighter's docs via our facts registry; ETH price from the Lighter API on October 22, 2026. Funding, slippage and adverse selection are not included. 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 and partner fees
Hosted bots (for example Dextrabot's Lighter grid bot, 2 to 36 grids in cross margin) trade through a Lighter API key you add. Before connecting any service, check how it charges: a platform that adds a non-zero integrator fee through Lighter's partner program (up to 0.1% on perps) needs your account on Plus or Premium, and those trades are excluded from Lighter's referral rebates; zero-fee attribution works on any account. Give it a dedicated sub-account, never your wallet seed phrase. Other venues: Aster has native grid trading, and Hyperliquid has the widest third-party bot ecosystem (perp DEX APIs compared, Hyperliquid trading bots).
FAQ
Where are the Lighter API docs?
At apidocs.lighter.xyz, which docs.lighter.xyz now also serves under /developers with an API reference under /api. Both list the REST base URL https://mainnet.zklighter.elliot.ai/api/v1/, the WebSocket at wss://mainnet.zklighter.elliot.ai/stream, the SDKs, rate limits and error codes, and every page is available as Markdown by adding .md (read October 22, 2026). API changes are announced in Lighter's Telegram API channel and Discord.
Does Lighter have an API?
Yes. Lighter has a public REST API, a WebSocket API, an official Python SDK (pip install lighter-sdk, 1.1.8 on October 22, 2026), a Go SDK (lighter-go) and a WebAssembly signer for TypeScript. Market data is public; placing orders requires an API key registered to your account index.
How do I get a Lighter API key?
Two ways. In the app: Tools → API Keys (app.lighter.xyz/apikeys), choose an index from 4–254, sign with your wallet and copy the private key, which is shown only once. With the SDK: generate a keypair with lighter.create_api_key() (or the system_setup.py example, after changing its default index of 3) and bind it to your account index with one signature from the Ethereum wallet that owns the account. Indexes 0–3 are reserved for Lighter's web and mobile apps, which leaves 251 keys per account. You need a funded Lighter account first.
Do I need an API key for Lighter market data?
No. Trades, order books, candles, funding rates and exchange stats are public: call REST 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.
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 on Plus, and on Premium 4,000 at 0 staked LIT rising to 72,000 at 500,000 LIT. WebSocket limits per IP: 255 connections, 500 subscriptions per connection, 5,000 in total and 200 client messages per minute (rate-limits page, read October 22, 2026).
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 there a Lighter SDK for TypeScript, Rust or Go?
Go: yes, lighter-go is official. TypeScript: the official route is the WebAssembly signer built from lighter-go; lighter-ts is a reference client, not a signer. Rust, Java and C++: no official SDK, but lighter-go ships examples that call its shared library. Unofficial SDKs exist for several languages; some add an integrator (partner) fee to your orders by default, which also requires a Plus or Premium account, so read their settings before you trade.
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.
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.004% maker / 0.028% taker at 0 staked LIT with 140 ms taker latency, and Plus is 0.005% on both sides with higher rate limits than Standard. TWAP orders cost 0.01% on Standard.
Is there a Lighter testnet?
Yes. Use https://testnet.zklighter.elliot.ai as the base URL (chain ID 300; mainnet 304) 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 happened. For taker orders the nonce still increases in that case.
Can third-party bots trade on a Lighter Standard account?
Yes if they charge you nothing through Lighter: zero-fee partner attribution works on any account type. A platform that adds a non-zero integrator fee (up to 0.1% on perps) through Lighter's partner program needs your account on Plus or Premium, and those trades are excluded from Lighter's referral rebates. A tool that simply uses your own API key without partner attribution is not affected.
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, you get 10% of your trading fees rebated every Monday.
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 API guide.
Disclosure: 718610TD is our Lighter referral code; we earn a share of fees paid by referred accounts (Premium fees, and paid advanced orders on Standard; plain Standard orders are free). Endpoints, limits, fees, SDK versions and error codes were checked in Lighter's API docs, SDK repositories, the app and the public API on October 22, 2026. Limits change, so check apidocs.lighter.xyz before you deploy. Nothing here is financial advice.