Limitless Exchange Python SDK
v1.0.11 | Async | Type-Safe | Partner HMAC Support
A minimalistic, async Python SDK for interacting with the Limitless Exchange API.
v1.0.11 Release: Adds authenticated profile reads via
/profiles/me, partner sub-account listing/recovery, and WebSocket subscription validation cleanup. See CHANGELOG.md for release notes.
Features
- 🔐 API Key authentication - Simple and secure authentication with API keys
- 🔏 HMAC-scoped partner authentication - Derived api-token v3 support for partner workflows
- 📈 Market data access - Markets, orderbooks, and historical data
- 🧭 Market pages navigation - Navigation tree, dynamic filters, property keys
- 📋 Order management - GTC, FAK, and FOK orders with automatic signing
- 🔢 IEEE-safe order payload parsing -
create_order()handlesmakerAmount,takerAmount,price, andsaltreturned as numeric strings - 💼 Portfolio tracking - Authenticated current profile reads, positions, and user history
- 🔄 Automatic retries - Configurable retry logic with error handling
- 🌐 WebSocket support - Real-time CLOB orderbook, AMM/oracle price, position, transaction, order-event, and market lifecycle updates
- 🤝 Partner account + delegated trading helpers - Server-wallet child accounts, account listing/recovery, delegated GTC/FAK/FOK order flows, withdrawal allowlists, and server-wallet redeem/withdraw to account, smart wallet, or whitelisted treasury destinations
- 🛡️ Custom headers - Global and per-request header configuration
- ⚡ Async/await support - Modern async Python with aiohttp
- 🚀 Venue caching - Automatic contract address caching for optimized order creation
⚠️ Disclaimer
USE AT YOUR OWN RISK
This SDK is provided "as-is" without any warranties or guarantees. Trading on prediction markets involves financial risk. By using this SDK, you acknowledge that:
- You are responsible for testing the SDK thoroughly before using it in production
- The SDK authors are not liable for any financial losses or damages
- You should review and understand the code before executing any trades
- It is recommended to test all functionality on testnet or with small amounts first
- The SDK may contain bugs or unexpected behavior despite best efforts
Feedback Welcome: We encourage you to report any bugs, suggest improvements, or contribute to the project. Please submit issues or pull requests on our GitHub repository.
🌍 Geographic Restrictions
Important: Limitless restricts order placement from US locations due to regulatory requirements and compliance with international sanctions. Before placing orders, builders should verify their location complies with applicable regulations.
Installation
pip install limitless-sdk
Quick Start
import asyncio
import os
from limitless_sdk.api import HttpClient
from limitless_sdk.markets import MarketFetcher
from limitless_sdk.portfolio import PortfolioFetcher
async def main():
# Setup - API key automatically loaded from LIMITLESS_API_KEY env variable
http_client = HttpClient(base_url="https://api.limitless.exchange")
try:
# Get markets
market_fetcher = MarketFetcher(http_client)
markets = await market_fetcher.get_active_markets()
print(f"Found {markets.total_markets_count} markets")
# Fetch specific market (caches venue data for orders)
market = await market_fetcher.get_market("bitcoin-2024")
print(f"Market: {market.title}")
# Get positions (requires authentication)
portfolio_fetcher = PortfolioFetcher(http_client)
positions = await portfolio_fetcher.get_positions()
print(f"CLOB positions: {len(positions['clob'])}")
finally:
await http_client.close()
if __name__ == "__main__":
asyncio.run(main())
Authentication
The SDK uses API keys for authentication. API keys can be obtained from your Limitless Exchange account settings.
Basic Authentication
import os
from limitless_sdk.api import HttpClient
# Option 1: Automatic from environment variable (recommended)
# Set LIMITLESS_API_KEY in your .env file or environment
http_client = HttpClient()
# Option 2: Explicit API key
http_client = HttpClient(
api_key=os.getenv("LIMITLESS_API_KEY")
)
# Option 3: Custom base URL (for dev/staging)
http_client = HttpClient(
base_url="https://staging.api.limitless.exchange",
api_key="sk_test_..."
)
# All requests automatically include X-API-Key header
Partner API Token v3 / HMAC Authentication
Use this flow when a partner first authenticates with a Privy identity token, derives a scoped API token, and then reuses the returned HMAC credentials for partner operations.
import asyncio
from limitless_sdk import (
Client,
DeriveApiTokenInput,
HMACCredentials,
ScopeTrading,
ScopeDelegatedSigning,
ScopeAccountCreation,
ScopeWithdrawal,
)
async def main():
identity_token = "privy-identity-token"
bootstrap = Client(base_url="https://api.limitless.exchange")
capabilities = await bootstrap.api_tokens.get_capabilities(identity_token)
print(capabilities.allowed_scopes)
derived = await bootstrap.api_tokens.derive_token(
identity_token,
DeriveApiTokenInput(
label="partner-bot",
scopes=[
ScopeTrading,
ScopeDelegatedSigning,
ScopeAccountCreation,
ScopeWithdrawal,
],
),
)
scoped = Client(
base_url="https://api.limitless.exchange",
hmac_credentials=HMACCredentials(
token_id=derived.token_id,
secret=derived.secret,
),
)
tokens = await scoped.api_tokens.list_tokens()
print(f"Active tokens: {len(tokens)}")
await bootstrap.close()
await scoped.close()
asyncio.run(main())
Partner surface added by this flow:
api_tokens.get_capabilities()api_tokens.derive_token()api_tokens.list_tokens()api_tokens.revoke_token()partner_accounts.create_account()partner_accounts.list_accounts()partner_accounts.check_allowances()partner_accounts.retry_allowances()partner_accounts.add_withdrawal_address()partner_accounts.delete_withdrawal_address()delegated_orders.create_order()delegated_orders.cancel_on_behalf_of()delegated_orders.cancel_all_on_behalf_of()server_wallets.redeem_positions()server_wallets.withdraw()
Standard X-API-Key authentication remains fully supported for the existing portfolio, market, and regular order flows.
Authenticated Profile Reads
Use client.portfolio.get_current_profile() to fetch the authenticated caller's private profile via GET /profiles/me. Use get_profile(address) when you need the existing address-based lookup via GET /profiles/:account.
from limitless_sdk import Client
client = Client(api_key="sk_test_...")
current_profile = await client.portfolio.get_current_profile()
profile_by_address = await client.portfolio.get_profile(
"0x1676716Ef7F19B5C5d690631CB57cf0bFD900A3d"
)
print(current_profile["id"])
await client.close()
Use partner HMAC credentials only in a backend or BFF service. Do not expose token_id / secret in browser bundles, frontend environment variables, or client-side storage.
Recommended setup:
- Keep public market and market-page reads in the browser.
- Store the real HMAC credentials on your backend.
- Use this SDK server-side to sign partner-authenticated requests.
- Expose only your own app-specific endpoints to the frontend.
Partner Account Listing
Use client.partner_accounts.list_accounts() from a backend or BFF with HMAC-scoped API-token credentials that include account_creation. This endpoint lists or recovers partner-owned child accounts created under the authenticated partner profile.
list_accounts()callsGET /profiles/partner-accounts- optional
accountfilters by exact account address - optional
limitandpageare positive integers;limitis capped to 25 before sending - the SDK requires HMAC credentials and does not send
x-on-behalf-of
from limitless_sdk import ListPartnerAccountsParams
accounts = await client.partner_accounts.list_accounts(
ListPartnerAccountsParams(
account="0x1676716Ef7F19B5C5d690631CB57cf0bFD900A3d",
limit=100,
page=1,
)
)
print(accounts.limit) # 25
print(accounts.data[0].profile_id)
Partner Server-Wallet Allowances
Use client.partner_accounts.check_allowances(profile_id) and client.partner_accounts.retry_allowances(profile_id) only for partner child profiles created with create_server_wallet=True.
check_allowances()callsGET /profiles/partner-accounts/:profileId/allowancesretry_allowances()callsPOST /profiles/partner-accounts/:profileId/allowances/retry- both operations require HMAC-scoped API-token auth with
account_creationanddelegated_signingscopes profile_idshould be the delegated child profile id
import asyncio
from limitless_sdk import Client, HMACCredentials
async def main():
client = Client(
base_url="https://api.limitless.exchange",
hmac_credentials=HMACCredentials(
token_id="token-id",
secret="token-secret",
),
)
try:
allowances = await client.partner_accounts.check_allowances(352)
if not allowances.ready:
# Retry re-checks live chain state and submits only targets still missing.
# A returned "submitted" status means this request submitted a sponsored tx/user operation.
allowances = await client.partner_accounts.retry_allowances(352)
print(allowances.ready)
finally:
await client.close()
asyncio.run(main())
Poll check_allowances() first. If ready is false and one or more targets are missing or failed with retryable=True, call retry_allowances(), then poll check_allowances() again after a short delay. Retry 429 responses raise RateLimitError and include retryAfterSeconds in error.response_data; retry 409 responses raise ConflictError, which means another retry is already running.
For a complete runnable flow, see examples/api_key_v3/partner_account_allowances.py.
Server Wallet Redeem & Withdraw
Use client.server_wallets only for server-managed wallets created in delegated-signing partner flows with create_server_wallet=True.
redeem_positions()callsPOST /portfolio/redeemwithdraw()callsPOST /portfolio/withdraw- both operations require HMAC-scoped API-token auth
withdraw()also requires thewithdrawalscope- set
on_behalf_ofto the delegated child profile id when withdrawing child server-wallet funds - omit
on_behalf_ofonly when withdrawing the authenticated caller's own server wallet to an explicitdestination amountfor withdraw must be provided in the token smallest unit- omit
destinationto use the API default: authenticated partner smart wallet when present, otherwise authenticated partner account - pass
destinationto withdraw directly to the authenticated partner account, authenticated partner smart wallet, or an active withdrawal address allowlisted on the authenticated partner profile partner_accounts.add_withdrawal_address()andpartner_accounts.delete_withdrawal_address()manage the allowlist with Privy identity-token auth; API-token auth is not used for those allowlist endpoints- in practice, redeem is most useful for an existing child profile that already traded in a now-resolved market
import asyncio
from limitless_sdk import Client, HMACCredentials
async def main():
client = Client(
base_url="https://api.limitless.exchange",
hmac_credentials=HMACCredentials(
token_id="token-id",
secret="token-secret",
),
)
try:
redeem = await client.server_wallets.redeem_positions(
condition_id="0x...",
on_behalf_of=352,
)
withdraw = await client.server_wallets.withdraw(
amount="5000000",
on_behalf_of=352,
)
print(redeem.transaction_id, withdraw.transaction_id)
finally:
await client.close()
asyncio.run(main())
To withdraw a partner child server wallet directly to a treasury address, allowlist the destination on the authenticated partner profile first. Use the same partner identity for the allowlist call and the same partner HMAC token for the withdraw call.
from limitless_sdk import PartnerWithdrawalAddressInput
identity_token = "privy-identity-token"
treasury_address = "0x0F3262730c909408042F9Da345a916dc0e1F9787"
await client.partner_accounts.add_withdrawal_address(
identity_token,
PartnerWithdrawalAddressInput(address=treasury_address, label="treasury"),
)
treasury_withdraw = await client.server_wallets.withdraw(
amount="5000000",
on_behalf_of=352,
destination=treasury_address,
)
own_wallet_withdraw = await client.server_wallets.withdraw(
amount="5000000",
destination=treasury_address,
)
await client.partner_accounts.delete_withdrawal_address(
identity_token,
treasury_address,
)
redeem.hash or withdraw.hash may be an empty string for user-operation submissions. Track those calls using user_operation_hash or transaction_id.
Environment Variables
Create a .env file in your project root:
# Required for authenticated endpoints
LIMITLESS_API_KEY=sk_live_your_api_key_here
# Optional: Custom API URL (defaults to production)
# LIMITLESS_API_URL=https://api.limitless.exchange
Custom HTTP Headers
You can configure custom headers globally (applied to ALL requests) or per-request:
# Global headers (applied to all requests)
http_client = HttpClient(
additional_headers={
"X-Custom-Header": "value",
"X-API-Version": "v1"
}
)
# Per-request headers (request ID, tracing, etc.)
response = await http_client.get("/endpoint", headers={"X-Request-ID": "123"})
Market Data
Get Markets
from limitless_sdk.markets import MarketFetcher
market_fetcher = MarketFetcher(http_client)
# Get active markets (paginated)
markets = await market_fetcher.get_active_markets({"page": 1, "limit": 50})
print(f"Total: {markets.total_markets_count}")
print(f"Markets: {len(markets.data)}")
# Get specific market (automatically caches venue data)
market = await market_fetcher.get_market("market-slug")
print(f"Title: {market.title}")
print(f"YES Token: {market.tokens.yes}")
print(f"NO Token: {market.tokens.no}")
# Venue data is now cached for efficient order creation
# Includes: exchange address (for signing) and adapter address (for NegRisk approvals)
Market Pages & Navigation
from limitless_sdk.api import HttpClient
from limitless_sdk.market_pages import MarketPageFetcher
http_client = HttpClient(base_url="https://api.limitless.exchange")
page_fetcher = MarketPageFetcher(http_client)
# 1) Navigation tree
navigation = await page_fetcher.get_navigation()
# 2) Resolve page by path (manual 301 handled internally)
page = await page_fetcher.get_market_page_by_path("/crypto")
# 3) Get markets with filters
markets = await page_fetcher.get_markets(
page.id,
{
"limit": 20,
"sort": "-updatedAt",
"filters": {"ticker": ["btc", "eth"]},
},
)
# 4) Property keys and options
property_keys = await page_fetcher.get_property_keys()
if property_keys:
key = await page_fetcher.get_property_key(property_keys[0].id)
options = await page_fetcher.get_property_options(key.id)
Get Orderbook
orderbook = await market_fetcher.get_orderbook("market-slug")
# Access bids/asks
for order in orderbook.get('orders', []):
print(f"Price: {order['price']}, Size: {order['size']}")
Token Approvals
Important: Before placing orders, you must approve tokens for the exchange contracts. This is a one-time setup per wallet.
Required Approvals
CLOB Markets:
- BUY orders: Approve USDC →
market.venue.exchange - SELL orders: Approve Conditional Tokens →
market.venue.exchange
NegRisk Markets:
- BUY orders: Approve USDC →
market.venue.exchange - SELL orders: Approve Conditional Tokens → both
market.venue.exchangeANDmarket.venue.adapter
Quick Setup
Run the approval setup script:
# Configure your wallet in .env
python examples/00_setup_approvals.py
Manual Approval Example
from web3 import Web3
from eth_account import Account
from limitless_sdk.markets import MarketFetcher
from limitless_sdk.utils.constants import get_contract_address
# 1. Fetch market to get venue addresses
market = await market_fetcher.get_market('market-slug')
# 2. Initialize Web3 and wallet
w3 = Web3(Web3.HTTPProvider('https://mainnet.base.org'))
account = Account.from_key(private_key)
# 3. Get contract addresses
usdc_address = get_contract_address("USDC", 8453)
ctf_address = get_contract_address("CTF", 8453)
# 4. Create contract instances
usdc = w3.eth.contract(address=usdc_address, abi=ERC20_APPROVE_ABI)
ctf = w3.eth.contract(address=ctf_address, abi=ERC1155_APPROVAL_ABI)
# 5. Approve USDC for BUY orders
max_uint256 = 2**256 - 1
tx = usdc.functions.approve(venue.exchange, max_uint256).build_transaction({...})
signed_tx = account.sign_transaction(tx)
w3.eth.send_raw_transaction(signed_tx.raw_transaction)
# 6. Approve CT for SELL orders
tx = ctf.functions.setApprovalForAll(venue.exchange, True).build_transaction({...})
signed_tx = account.sign_transaction(tx)
w3.eth.send_raw_transaction(signed_tx.raw_transaction)
# 7. For NegRisk SELL orders, also approve adapter
if market.neg_risk_request_id:
tx = ctf.functions.setApprovalForAll(venue.adapter, True).build_transaction({...})
signed_tx = account.sign_transaction(tx)
w3.eth.send_raw_transaction(signed_tx.raw_transaction)
For complete examples with proper ABIs and transaction handling, see examples/00_setup_approvals.py.
Collateral Fee Venues
Some markets trade on collateral fee venues, which charge the taker fee in USDC through a separate fee module contract. Markets on legacy venues behave exactly as before.
-
Detect the venue.
market.venuecarriesfee_module(feeModule) andfee_module_type(feeModuleType) on collateral fee venues; legacy venues omit both.feeModuleType == "COLLATERAL"means a collateral fee venue:from limitless_sdk import is_collateral_fee_venue market = await market_fetcher.get_market('market-slug') if is_collateral_fee_venue(market.venue): fee_module = market.venue.fee_module
-
Approve USDC to the fee module. The wallet that signs orders must call
USDC.approve(venue.fee_module, amount)(max uint recommended). This is required for every order that is notpost_only(takers, and resting orders not flaggedpost_only), for both BUY and SELL;post_onlyorders don't need it. Without it the API rejects the order withInsufficient collateral allowance for the order fee.One approval is needed per fee module address. SIMPLE and NegRisk markets use different fee modules, so always readvenue.fee_modulefrom the market and never hardcode it.examples/00_setup_approvals.pyhandles this step. -
Keep your existing approvals. Collateral fee venues use the same exchange (and NegRisk adapter) contracts, so the USDC and Conditional Token approvals above stay valid. The EIP-712 order domain (verifying contract =
venue.exchange) is unchanged. -
Sign
feeRateBps = 0. Any other value is rejected withFEE_RATE_MISMATCH.OrderClient.create_order,OrderClient.cancel_replaceand theDelegatedOrderServicemethods do this automatically from the market venue.OrderClient.build_unsigned_orderandOrderBuilderdon't know the venue: on collateral fee venues setfee_rate_bpsto 0 yourself before signing. Legacy venues keep the current fee rate behavior. -
Keep a little extra USDC. BUY orders on collateral fee venues need enough USDC for the order plus the trading fee.
-
Managed wallets need no action. Server wallets and other wallets that Limitless provisions and manages get the fee module approval automatically.
Order Management
The SDK supports three order types:
- GTC (Good-Till-Cancelled): Uses
price+sizeparameters - FAK (Fill-And-Kill): Uses
price+sizeand cancels any unmatched remainder - FOK (Fill-Or-Kill): Uses
maker_amount(total USDC to spend/receive)
Create GTC Orders
from limitless_sdk.orders import OrderClient
from limitless_sdk.types import Side, OrderType
# Setup order client (userData fetched automatically from profile)
order_client = OrderClient(
http_client=http_client,
wallet=account,
)
# Get token ID from market
token_id = str(market.tokens.yes) # or market.tokens.no
# Create BUY GTC order
order = await order_client.create_order(
token_id=token_id,
price=0.50, # Minimum acceptable price
size=5.0, # Number of shares
side=Side.BUY,
order_type=OrderType.GTC,
market_slug=market.slug,
post_only=True, # Optional. Supported only for GTC orders
)
print(f"Order ID: {order.order.id}")
print(f"Status: {order.order.status}")
Create FAK Orders
FAK (Fill-And-Kill) orders use the same price/size construction as GTC, but any unmatched remainder is cancelled immediately instead of resting on the orderbook.
# FAK BUY order - fill up to 5 shares at the limit price, cancel remainder
order = await order_client.create_order(
token_id=token_id,
price=0.45,
size=5.0,
side=Side.BUY,
order_type=OrderType.FAK,
market_slug=market.slug
)
if order.maker_matches:
print(f"Immediate matches: {len(order.maker_matches)}")
else:
print("No immediate match. Remaining size was cancelled.")
Create FOK Orders
FOK (Fill-Or-Kill) orders either execute immediately and completely or are cancelled. They use maker_amount instead of price/size parameters.
Parameter Semantics:
- BUY orders:
maker_amount= total USDC to spend (e.g., 10.0 = $10 USDC) - SELL orders:
maker_amount= number of shares to sell (e.g., 18.64 shares)
# FOK BUY order - spend $10 USDC
order = await order_client.create_order(
token_id=token_id,
maker_amount=10.0, # Spend $10 USDC
side=Side.BUY,
order_type=OrderType.FOK,
market_slug=market.slug
)
# FOK SELL order - sell 18.64 shares
order = await order_client.create_order(
token_id=token_id,
maker_amount=18.64, # Sell 18.64 shares
side=Side.SELL,
order_type=OrderType.FOK,
market_slug=market.slug
)
# Check if filled
if order.maker_matches and len(order.maker_matches) > 0:
print(f"FILLED: {len(order.maker_matches)} matches")
else:
print("NOT FILLED (cancelled)")
Cancel Orders
# Cancel single order by ID
await order_client.cancel(order_id)
# Cancel all orders for a market
await order_client.cancel_all(market_slug)
Cancel-Replace Orders
Atomically cancel a resting order and submit its replacement in a single request via POST /orders/cancel-replace. Identify the order to cancel with order_id or client_order_id, and set mode to CancelReplaceMode.STOP_ON_FAILURE (skip the replacement if the cancel fails) or CancelReplaceMode.ALLOW_FAILURE.
from limitless_sdk import CancelReplaceMode, OrderType, Side
result = await order_client.cancel_replace(
order_id="old-order-id", # or client_order_id="..."
mode=CancelReplaceMode.STOP_ON_FAILURE,
token_id="123",
side=Side.BUY,
order_type=OrderType.GTC,
market_slug="market-slug",
price=0.5,
size=2,
)
# result.cancel and result.replacement each carry a per-leg status.
Replace many orders at once with order_client.cancel_replace_batch(operations=[...]), passing a list of dicts with the same keyword arguments; the response results are index-aligned to the input. Partner integrations use delegated_orders.cancel_replace / cancel_replace_batch (which accept on_behalf_of). The single-order variant accepts a 409 conflict as a returned result rather than raising.
Partner AMM Trading
client.partner_amm trades binary AMM (FPMM) markets on behalf of a server wallet. Approvals are set up once per wallet/market pair; buy and sell never preflight allowances. All amounts are positive integer strings in the collateral token's base units (never floats). Authentication uses an HMAC API token (scopes trading + delegated_signing) or a per-call Privy identity_token; legacy API keys are rejected.
from limitless_sdk import Client, AmmAllowanceParams, AmmBuyParams, AmmSellParams
client = Client(hmac_credentials={"token_id": TOKEN_ID, "secret": SECRET})
# 1. One-time approval setup for a wallet/market pair (BUY and SELL are independent).
# ensure_allowance checks, approves at most once, then polls check until confirmed.
await client.partner_amm.ensure_allowance(
AmmAllowanceParams(market="market-slug", side="BUY", on_behalf_of=12345)
)
await client.partner_amm.ensure_allowance(
AmmAllowanceParams(market="market-slug", side="SELL", on_behalf_of=12345)
)
# 2. Buy: spend an exact collateral amount on outcome 0 (YES).
buy = await client.partner_amm.buy(
AmmBuyParams(
market="market-slug",
outcome_index=0, # 0 = YES, 1 = NO
collateral_amount="1000000", # base units, positive integer string
slippage_bps=100, # optional, 0..1000, defaults to 100
idempotency_key="buy-unique-key-001", # required; reuse exact key + body to retry safely
on_behalf_of=12345, # omit for a direct profile
)
)
print(buy.status, buy.expected_shares, buy.min_shares)
# 3. Sell: request an exact collateral return.
sell = await client.partner_amm.sell(
AmmSellParams(
market="market-slug",
outcome_index=0,
collateral_return_amount="992015",
idempotency_key="sell-unique-key-001",
on_behalf_of=12345,
)
)
print(sell.status, sell.expected_shares, sell.max_shares)
Notes:
ensure_allowancepollscheck_allowance(default every 2s, up to 30 attempts; tune withinterval/max_attempts). A202 submittedapprove response is not confirmation.- Reuse the same params on a timeout retry — the serialized body and
idempotency_keystay byte-identical, so the server replays the original submission. Reusing a key with different params raisesConflictError(409). - Errors map to typed classes:
ValidationError(400),AuthenticationError(401/403),ConflictError(409),UnprocessableEntityError(422, e.g. insufficient balance/invalid quote),TooEarlyError(425, maintenance),RateLimitError(429), andUpstreamUnavailableError(502/503). The four AMM routes share a limit of 10 requests / 10s per actor. - Pass
with_raw_response=Trueto any AMM method to get anHttpRawResponseexposing the HTTPstatus,headers, and originaldata.
Portfolio
Get Positions
from limitless_sdk.portfolio import PortfolioFetcher
portfolio_fetcher = PortfolioFetcher(http_client)
# Get authenticated profile
profile = await portfolio_fetcher.get_current_profile()
print(f"Profile ID: {profile['id']}")
# Get positions
positions = await portfolio_fetcher.get_positions()
# Access CLOB positions
clob_positions = positions['clob']
for position in clob_positions:
print(f"Market: {position['market']['title']}")
print(f"Size: {position['size']}")
# Access points
print(f"Points: {positions['accumulativePoints']}")
WebSocket Support
Subscribe to backend-supported websocket events only. Public subscriptions include subscribe_market_prices, subscribe_live_sports, subscribe_live_esports, and subscribe_market_lifecycle; authenticated subscriptions include subscribe_positions, subscribe_transactions, and subscribe_order_events.
Each subscribe_market_prices or subscribe_positions call replaces that
channel's previous market selection. To follow multiple markets, include them
in one payload, such as {'marketSlugs': ['market-a', 'market-b']}. Automatic
reconnects restore only the latest payload per channel.
from limitless_sdk.websocket import WebSocketClient, WebSocketConfig
# Setup WebSocket
config = WebSocketConfig(
url="wss://ws.limitless.exchange",
auto_reconnect=True,
reconnect_delay=1.0
)
ws_client = WebSocketClient(config=config)
# Event handlers
@ws_client.on('connect')
async def on_connect():
print("Connected")
@ws_client.on('orderbookUpdate')
async def on_orderbook_update(data):
orderbook = data.get('orderbook', data)
best_bid = orderbook['bids'][0]['price']
best_ask = orderbook['asks'][0]['price']
print(f"Bid: {best_bid:.4f} | Ask: {best_ask:.4f}")
@ws_client.on('newPriceData')
async def on_amm_price_update(data):
for price in data.get('updatedPrices', []):
print(f"AMM {price['marketAddress']}: YES={price['yesPrice']:.4f}")
@ws_client.on('orderEvent')
async def on_order_event(data):
# Both frames arrive on `orderEvent`; discriminate on `source` + `type`.
source, event_type = data.get('source'), data.get('type')
if source == 'OME' and event_type == 'EXECUTION':
# FAK/FOK terminal frame. eventId is "terminal:<orderId>".
# price / remainingSize are JSON numbers (float).
print(f"Execution {data['status']}: remaining {data['remainingSize']}")
elif source == 'SETTLEMENT' and event_type == 'MATCHED':
# Pre-settlement per-fill estimate (isEstimate == True).
# Maker side reports fee 0; taker reports a real estimate.
print(f"Matched {data['token']} @ {data['price']}")
else:
# Lifecycle frames: OME PLACEMENT/UPDATE/CANCELLATION, SETTLEMENT MINED/FAILED.
print(f"Order event {source}/{event_type}")
# Connect and subscribe
await ws_client.connect()
await ws_client.subscribe('subscribe_market_prices', {'marketSlugs': [market_slug]})
Order events (subscribe_order_events) arrive on orderEvent and cover two
unions discriminated by source + type:
- OME frames carry
typeofPLACEMENT,UPDATE,CANCELLATION, orEXECUTION(the FAK/FOK terminal frame, which adds astatusofFILLED,PARTIALLY_FILLED, orKILLED).priceandremainingSizeare JSON numbers. - SETTLEMENT frames carry
typeofMINED,FAILED, orMATCHED(the pre-settlement per-fill estimate, withisEstimate=Trueand atokenofYES/NO). OnMATCHEDthe maker side reports a fee of 0; the taker reports a real estimate.
Error Handling
The SDK provides APIError for all API-related errors:
from limitless_sdk.api import APIError
try:
order = await order_client.create_order(...)
except APIError as e:
print(f"Status: {e.status_code}")
print(f"Error: {e}") # Prints raw API response JSON
Retry Mechanism
Use the @retry_on_errors decorator for custom retry logic:
from limitless_sdk.api import retry_on_errors
@retry_on_errors(
status_codes={500, 429},
max_retries=3,
delays=[1, 2, 3],
on_retry=lambda attempt, error, delay: print(f"Retry {attempt+1}/3")
)
async def fetch_data():
return await http_client.get("/endpoint")
Logging
Enable debug logging to see request headers and details:
from limitless_sdk.types import ConsoleLogger, LogLevel
logger = ConsoleLogger(level=LogLevel.DEBUG)
http_client = HttpClient(base_url="...", logger=logger)
Architecture
The SDK is organized into modular components:
Core Components
HttpClient: Low-level HTTP client with API key authentication and retry logicOrderSigner: EIP-712 message signing for order creationRetryableClient: Auto-retry wrapper with configurable retry strategies
Domain Components
MarketFetcher: Market data retrieval (markets, orderbooks)OrderClient: Order creation/cancellation with automatic signingPortfolioFetcher: Portfolio and positions dataWebSocketClient: Real-time CLOB orderbook, AMM/oracle price, position, transaction, order-event, and market lifecycle updates
Type System
The SDK uses Pydantic models for type safety:
UserProfile: User account informationSide:BUY/SELLenumOrderType:GTC/FAK/FOKenumLogLevel:DEBUG/INFO/WARN/ERRORenumMarket: Market metadata and configuration
Examples
See the examples/ directory for complete working examples:
01_authentication.py- API key authentication with portfolio data02_create_buy_gtc_order.py- Create BUY GTC order03_cancel_gtc_order.py- Cancel orders (single or all)04_create_sell_gtc_order.py- Create SELL GTC order05_create_buy_fok_order.py- Create BUY FOK order06_create_sell_fok_order.py- Create SELL FOK order10_create_buy_fak_order.py- Create BUY FAK order06_retry_handling.py- Custom retry logic with@retry_on_errors07_auto_retry_second_sample.py- Auto-retry withRetryableClient08_websocket_events.py- Real-time orderbook updatesexamples/api_key_v3/README.md- Partner HMAC examples, including delegated GTC/FAK/FOK order flows, allowance recovery, and server-wallet redeem/withdraw
Development
Setup
git clone https://github.com/limitless-labs-group/limitless-exchange-ts-sdk.git
cd limitless-sdk
pip install -e ".[dev]"
Testing
pytest
Linting
ruff check .
mypy limitless_sdk/
License
MIT License - see LICENSE file for details.
Support
For questions or issues:
- GitHub Issues: Create an issue
Key Features
Venue Caching System
The SDK automatically caches venue data (exchange and adapter contract addresses) to optimize performance when creating multiple orders for the same market.
How it works:
# Fetch market once
market_fetcher = MarketFetcher(http_client)
market = await market_fetcher.get_market("bitcoin-2024")
# Venue data is now cached automatically
# {
# exchange: "0xa4409D988CA2218d956BeEFD3874100F444f0DC3", # for order signing
# adapter: "0x5a38afc17F7E97ad8d6C547ddb837E40B4aEDfC6" # for NegRisk approvals
# }
# Create order client (userData fetched automatically from profile on first order)
order_client = OrderClient(http_client, wallet)
# Venue is fetched from cache (no API call)
# User data is fetched automatically on first order creation
order1 = await order_client.create_order(
token_id=str(market.tokens.yes),
price=0.50,
size=5.0,
side=Side.BUY,
order_type=OrderType.GTC,
market_slug=market.slug
)
# Still using cached venue data and user data
order2 = await order_client.create_order(
token_id=str(market.tokens.no),
price=0.30,
size=10.0,
side=Side.BUY,
order_type=OrderType.GTC,
market_slug=market.slug
)
Performance benefits:
- Eliminates redundant
/venues/:slugAPI calls - Faster order creation (cache hit vs network request)
- Reduced API rate limit usage
Debug logging: Enable debug mode to see venue cache operations:
logger = ConsoleLogger(level=LogLevel.DEBUG)
http_client = HttpClient(base_url="...", logger=logger)
# You'll see:
# [Limitless SDK] Venue cached for order signing {
# slug: 'bitcoin-2024',
# exchange: '0xa4409D988CA2218d956BeEFD3874100F444f0DC3',
# adapter: '0x5a38afc17F7E97ad8d6C547ddb837E40B4aEDfC6',
# cacheSize: 1
# }
# [Limitless SDK] Venue cache hit { slug: 'bitcoin-2024', exchange: '0xa4...' }
Token ID Extraction
CLOB markets use a tokens object for YES/NO positions:
# Get YES token ID
token_id = str(market.tokens.yes)
# Get NO token ID
token_id = str(market.tokens.no)
Raw API Responses
The SDK returns raw API responses without heavy parsing, allowing direct access to all fields:
# Markets response
markets = await market_fetcher.get_markets()
total = markets['totalCount']
data = markets['data']
# Positions response
positions = await portfolio_fetcher.get_positions()
clob = positions['clob']
points = positions['accumulativePoints']
Order Type Parameters
-
GTC orders: Use
price+sizeparametersprice=0.50, # Minimum acceptable price (0-1 range) size=5.0 # Number of shares to buy/sell
-
FAK orders: Use
price+sizeparameters and cancel any unfilled remainderprice=0.45, # Limit price size=5.0 # Shares to fill immediately if available
-
FOK orders: Use
maker_amountparameter (semantics differ by side)# BUY: Total USDC to spend maker_amount=10.0 # Spend $10 USDC to buy shares # SELL: Number of shares to sell maker_amount=18.64 # Sell 18.64 shares for USDC
Changelog
v1.2.0
- Collateral fee venue support:
Venue.fee_module/Venue.fee_module_type, plusis_collateral_fee_venue(venue)andFEE_MODULE_TYPE_COLLATERALexported fromlimitless_sdk. OrderClient,DelegatedOrderServiceand the legacyLimitlessClientsignfeeRateBps = 0on collateral fee venues; legacy venues are unchanged.DelegatedOrderServiceaccepts a sharedmarket_fetcherand resolves the market venue from its cache (fetching once per market on a miss).examples/00_setup_approvals.pyapproves USDC tovenue.feeModuleon collateral fee venues.
See CHANGELOG.md for details.
v1.1.1
- Restore subscriptions and refresh HMAC authentication on automatic WebSocket reconnects.
- Keep SDK lifecycle handling active when user connection callbacks are registered or removed.
- Replay only the latest subscription payload per channel, matching the backend's market-selection replacement behavior.
See CHANGELOG.md for details.
v1.1.0
Release Date: June 8, 2026
Adds the OME EXECUTION (FAK/FOK terminal) and Settlement MATCHED (pre-settlement per-fill) WebSocket frames, and corrects the OME numeric field types.
Highlights
- OME
EXECUTIONframe:orderEventnow models the FAK/FOK terminal frame withtype="EXECUTION", astatusofFILLED/PARTIALLY_FILLED/KILLED, and a stringeventIdof"terminal:<orderId>". - Settlement
MATCHEDframe:orderEventnow models the pre-settlement per-fill estimate withtype="MATCHED",isEstimate, andtoken(YES/NO). - Type fix (breaking, type-only): OME
price/remainingSizeare nowfloatandeventIdisint | str. Runtime values never changed — OME frames always emitted these as JSON numbers; only the static types were wrong.
v1.0.11
Release Date: May 27, 2026
Latest release with authenticated profile reads, partner sub-account listing/recovery, and WebSocket subscription validation cleanup.
Highlights
- Authenticated Profiles: Fetch the current authenticated profile with
GET /profiles/me. - Partner Account Listing:
partner_accounts.list_accounts()lists partner-owned sub-accounts with optional address recovery and pagination. - WebSocket Channel Validation: Unsupported legacy short channel literals now fail fast.
v1.0.10
Release Date: May 12, 2026
Latest release with authenticated WebSocket HMAC fixes for Python Socket.IO clients.
Highlights
- WebSocket HMAC Auth: Authenticated subscriptions now sign the Engine.IO WebSocket path emitted by
python-engineioand use a stable WebSocket upgrade URL.
v1.0.9
Release Date: May 4, 2026
Release with partner withdrawal-address allowlist helpers, server-wallet withdrawals to explicit whitelisted treasury destinations, and expanded WebSocket event coverage.
Highlights
- Partner Withdrawal Allowlists:
partner_accounts.add_withdrawal_address()anddelete_withdrawal_address()use Privy identity auth for/portfolio/withdrawal-addresses. - Treasury Withdrawals:
server_wallets.withdraw()supports child server-wallet withdrawals to allowlisted destinations and caller-wallet withdrawals withdestinationonly. - Expanded WebSocket Surface: Added typed subscription/event coverage for order events, live sports/esports, market lifecycle, oracle price data, and system messages.
v1.0.0
Release Date: January 2026
This is the first stable, production-ready release of the Limitless Exchange Python SDK, designated as a Long-Term Support (LTS) version. This release consolidates all features and improvements from pre-release versions into a stable, well-documented, and thoroughly tested SDK.
Core Features
-
🔐 Authentication & Security
- API key authentication with X-API-Key header
- EIP-712 message signing for order creation
OrderSignerfor cryptographic order signing operationsAuthenticationErrorfor authentication failure handling- Secure API key management from environment variables
-
📊 Market Data Access
MarketFetcherwith intelligent venue caching system- Active markets retrieval with pagination and sorting
- Market-specific data fetching (slug-based)
- Real-time orderbook data
- Automatic venue data caching for performance optimization
- Cache-aware market operations (eliminates redundant API calls)
-
📋 Order Management
OrderClientfor comprehensive order operations- GTC Orders (Good-Till-Cancelled):
price+sizeparameters- Optional
post_only=Truerejects the order if it would match immediately
- Optional
- FAK Orders (Fill-And-Kill):
price+sizeparameters, remainder cancelled - FOK Orders (Fill-Or-Kill):
maker_amountparameter- BUY: maker_amount = total USDC to spend
- SELL: maker_amount = number of shares to sell
- Automatic EIP-712 order signing with venue.exchange integration
- Dynamic venue resolution from cache or API
- Order cancellation (single order and batch operations)
- Maker match tracking and order status monitoring
-
💼 Portfolio Management
PortfolioFetcherfor position tracking- CLOB position data retrieval
- User history access
- Accumulative points tracking
- Portfolio-wide analytics
-
🌐 WebSocket Support
WebSocketClientfor real-time orderbook updates- Event-based subscription system with decorators
- Auto-reconnect functionality with configurable delays
- Typed event handlers for orderbook updates
- Connection lifecycle management
-
🔄 Retry & Error Handling
@retry_on_errorsdecorator with customizable retry logicRetryableClientfor automatic retry on transient failures- Configurable delays and maximum retry attempts
- Status code-based retry strategies
- Comprehensive
APIErrorexception hierarchy (AuthenticationError,RateLimitError,ValidationError)
-
📝 Logging & Debugging
ConsoleLoggerwith configurable log levels (DEBUG, INFO, WARN, ERROR)- Enhanced debug logging for venue operations
- Venue cache monitoring (hits/misses)
- Request/response logging with header visibility
- Performance tracking and observability
-
🛡️ Token Approval System
- Complete token approval setup guide
- CLOB market approval workflows
- NegRisk market dual-approval requirements
- Web3 integration examples
- ERC-20 (USDC) and ERC-1155 (Conditional Tokens) support
Performance & Optimization
- Venue Caching: Automatic venue data caching eliminates redundant API calls
- Connection Pooling: Efficient HTTP client with aiohttp connection pooling
- Async/Await: Full async support for optimal performance
- Session Reuse: Persistent HTTP sessions for improved performance
- Custom Headers: Global and per-request header configuration
Documentation & Examples
-
Comprehensive README: 650+ lines covering all features
-
11 Working Examples:
00_setup_approvals.py- Token approval setup01_authentication.py- API key authentication with portfolio data02_create_buy_gtc_order.py- GTC BUY orders03_cancel_gtc_order.py- Order cancellation04_create_sell_gtc_order.py- GTC SELL orders05_create_buy_fok_order.py- FOK BUY orders06_create_sell_fok_order.py- FOK SELL orders10_create_buy_fak_order.py- FAK BUY orders06_retry_handling.py- Custom retry logic07_auto_retry_second_sample.py- Auto-retry patterns with RetryableClient08_websocket_events.py- Real-time WebSocket events
-
Documentation Quality Improvements:
- Accurate FOK order parameter documentation (BUY vs SELL semantics)
- Clear GTC order price parameter explanations and
post_onlyusage - FAK order examples and semantics documentation
- Comprehensive venue system documentation
- Token approval requirements by market type
- Best practices for venue caching and performance
Architecture
- Modular Design: Clean separation of concerns with focused components
- Type Safety: Full Pydantic model integration for type validation
- Extensibility: Easy to extend with custom authentication or signing logic
- Standards Compliance: Follows Python async best practices
Quality Assurance
- Production-ready code quality
- Comprehensive error handling
- Well-documented public APIs
- Consistent coding patterns
- Validated against live Base mainnet
Breaking Changes from Pre-Release
None - this is the first stable release. All pre-release versions (v0.x) were development versions leading to this LTS release.
Pre-Release Versions
The following versions were development releases leading to v1.0.0:
v0.3.1 (Pre-release)
- Venue caching system implementation
- Enhanced debug logging
- Venue system documentation
v0.3.0 (Pre-release)
- Modular architecture refactor
- WebSocket support
- Enhanced authentication system
- HTTP client improvements
- Order system enhancements
v0.2.0 (Pre-release)
- Added
additional_headersparameter toHttpClient - Global and per-request header configuration
RetryableClientfor automatic retry on transient failures- WebSocket support for real-time updates
- Retry decorator (
@retry_on_errors) - Comprehensive examples directory
- Fixed license configuration in pyproject.toml
v0.1.0 (Pre-release)
- Initial release
- API key authentication with X-API-Key header
- EIP-712 signing for order creation
- Market data access
- GTC, FAK, and FOK order support
- Portfolio tracking
LTS Support Policy
v1.0.0 LTS will receive:
- Security updates and critical bug fixes
- Compatibility maintenance with Limitless Exchange API
- Community support and issue resolution
- Documentation updates and improvements
For production deployments, we recommend using the LTS version for stability and long-term support.
Metadata
Release files for limitless-sdk 1.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| limitless_sdk-1.2.0.tar.gz | 139.1 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| limitless_sdk-1.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 246.0 kB
Release files / limitless_sdk-1.2.0.tar.gz
| Download URL | limitless_sdk-1.2.0.tar.gz |
|---|---|
| Size | 139.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
3b562314d81a4f562212a0fd6cfad193810b71e2327161b6dcad4a24279c29cf
|
|
BLAKE2b-256 checksum How to use checksums |
58b6a702b5372186e540f5e1b43614cc80418f71f6f73bd9ac10c4ff65b0bca7
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.11.13
|
Release files / limitless_sdk-1.2.0-py3-none-any.whl
| Download URL | limitless_sdk-1.2.0-py3-none-any.whl |
|---|---|
| Size | 106.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
13f5019f2fef4fb17b2a510eed75a565e583c9a6331b26c27fd1699d7fcf0d59
|
|
BLAKE2b-256 checksum How to use checksums |
003518c5b96b6d95b476adc773e8b68f848d4f88ea26438aa1ba087cbde2aa59
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.11.13
|