Skip to main content

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() handles makerAmount, takerAmount, price, and salt returned 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() calls GET /profiles/partner-accounts
  • optional account filters by exact account address
  • optional limit and page are positive integers; limit is 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() calls GET /profiles/partner-accounts/:profileId/allowances
  • retry_allowances() calls POST /profiles/partner-accounts/:profileId/allowances/retry
  • both operations require HMAC-scoped API-token auth with account_creation and delegated_signing scopes
  • profile_id should 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() calls POST /portfolio/redeem
  • withdraw() calls POST /portfolio/withdraw
  • both operations require HMAC-scoped API-token auth
  • withdraw() also requires the withdrawal scope
  • set on_behalf_of to the delegated child profile id when withdrawing child server-wallet funds
  • omit on_behalf_of only when withdrawing the authenticated caller's own server wallet to an explicit destination
  • amount for withdraw must be provided in the token smallest unit
  • omit destination to use the API default: authenticated partner smart wallet when present, otherwise authenticated partner account
  • pass destination to 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() and partner_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.exchange AND market.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.

  1. Detect the venue. market.venue carries fee_module (feeModule) and fee_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
    
  2. 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 not post_only (takers, and resting orders not flagged post_only), for both BUY and SELL; post_only orders don't need it. Without it the API rejects the order with Insufficient collateral allowance for the order fee. One approval is needed per fee module address. SIMPLE and NegRisk markets use different fee modules, so always read venue.fee_module from the market and never hardcode it. examples/00_setup_approvals.py handles this step.

  3. 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.

  4. Sign feeRateBps = 0. Any other value is rejected with FEE_RATE_MISMATCH. OrderClient.create_order, OrderClient.cancel_replace and the DelegatedOrderService methods do this automatically from the market venue. OrderClient.build_unsigned_order and OrderBuilder don't know the venue: on collateral fee venues set fee_rate_bps to 0 yourself before signing. Legacy venues keep the current fee rate behavior.

  5. Keep a little extra USDC. BUY orders on collateral fee venues need enough USDC for the order plus the trading fee.

  6. 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 + size parameters
  • FAK (Fill-And-Kill): Uses price + size and 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_allowance polls check_allowance (default every 2s, up to 30 attempts; tune with interval / max_attempts). A 202 submitted approve response is not confirmation.
  • Reuse the same params on a timeout retry — the serialized body and idempotency_key stay byte-identical, so the server replays the original submission. Reusing a key with different params raises ConflictError (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), and UpstreamUnavailableError (502/503). The four AMM routes share a limit of 10 requests / 10s per actor.
  • Pass with_raw_response=True to any AMM method to get an HttpRawResponse exposing the HTTP status, headers, and original data.

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 type of PLACEMENT, UPDATE, CANCELLATION, or EXECUTION (the FAK/FOK terminal frame, which adds a status of FILLED, PARTIALLY_FILLED, or KILLED). price and remainingSize are JSON numbers.
  • SETTLEMENT frames carry type of MINED, FAILED, or MATCHED (the pre-settlement per-fill estimate, with isEstimate=True and a token of YES/NO). On MATCHED the 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 logic
  • OrderSigner: EIP-712 message signing for order creation
  • RetryableClient: Auto-retry wrapper with configurable retry strategies

Domain Components

  • MarketFetcher: Market data retrieval (markets, orderbooks)
  • OrderClient: Order creation/cancellation with automatic signing
  • PortfolioFetcher: Portfolio and positions data
  • WebSocketClient: 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 information
  • Side: BUY / SELL enum
  • OrderType: GTC / FAK / FOK enum
  • LogLevel: DEBUG / INFO / WARN / ERROR enum
  • Market: Market metadata and configuration

Examples

See the examples/ directory for complete working examples:

  • 01_authentication.py - API key authentication with portfolio data
  • 02_create_buy_gtc_order.py - Create BUY GTC order
  • 03_cancel_gtc_order.py - Cancel orders (single or all)
  • 04_create_sell_gtc_order.py - Create SELL GTC order
  • 05_create_buy_fok_order.py - Create BUY FOK order
  • 06_create_sell_fok_order.py - Create SELL FOK order
  • 10_create_buy_fak_order.py - Create BUY FAK order
  • 06_retry_handling.py - Custom retry logic with @retry_on_errors
  • 07_auto_retry_second_sample.py - Auto-retry with RetryableClient
  • 08_websocket_events.py - Real-time orderbook updates
  • examples/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:

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/:slug API 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 + size parameters

    price=0.50,  # Minimum acceptable price (0-1 range)
    size=5.0     # Number of shares to buy/sell
    
  • FAK orders: Use price + size parameters and cancel any unfilled remainder

    price=0.45,  # Limit price
    size=5.0     # Shares to fill immediately if available
    
  • FOK orders: Use maker_amount parameter (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, plus is_collateral_fee_venue(venue) and FEE_MODULE_TYPE_COLLATERAL exported from limitless_sdk.
  • OrderClient, DelegatedOrderService and the legacy LimitlessClient sign feeRateBps = 0 on collateral fee venues; legacy venues are unchanged.
  • DelegatedOrderService accepts a shared market_fetcher and resolves the market venue from its cache (fetching once per market on a miss).
  • examples/00_setup_approvals.py approves USDC to venue.feeModule on 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 EXECUTION frame: orderEvent now models the FAK/FOK terminal frame with type="EXECUTION", a status of FILLED/PARTIALLY_FILLED/KILLED, and a string eventId of "terminal:<orderId>".
  • Settlement MATCHED frame: orderEvent now models the pre-settlement per-fill estimate with type="MATCHED", isEstimate, and token (YES/NO).
  • Type fix (breaking, type-only): OME price/remainingSize are now float and eventId is int | 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-engineio and 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() and delete_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 with destination only.
  • 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
    • OrderSigner for cryptographic order signing operations
    • AuthenticationError for authentication failure handling
    • Secure API key management from environment variables
  • 📊 Market Data Access

    • MarketFetcher with 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

    • OrderClient for comprehensive order operations
    • GTC Orders (Good-Till-Cancelled): price + size parameters
      • Optional post_only=True rejects the order if it would match immediately
    • FAK Orders (Fill-And-Kill): price + size parameters, remainder cancelled
    • FOK Orders (Fill-Or-Kill): maker_amount parameter
      • 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

    • PortfolioFetcher for position tracking
    • CLOB position data retrieval
    • User history access
    • Accumulative points tracking
    • Portfolio-wide analytics
  • 🌐 WebSocket Support

    • WebSocketClient for 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_errors decorator with customizable retry logic
    • RetryableClient for automatic retry on transient failures
    • Configurable delays and maximum retry attempts
    • Status code-based retry strategies
    • Comprehensive APIError exception hierarchy (AuthenticationError, RateLimitError, ValidationError)
  • 📝 Logging & Debugging

    • ConsoleLogger with 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:

    1. 00_setup_approvals.py - Token approval setup
    2. 01_authentication.py - API key authentication with portfolio data
    3. 02_create_buy_gtc_order.py - GTC BUY orders
    4. 03_cancel_gtc_order.py - Order cancellation
    5. 04_create_sell_gtc_order.py - GTC SELL orders
    6. 05_create_buy_fok_order.py - FOK BUY orders
    7. 06_create_sell_fok_order.py - FOK SELL orders
    8. 10_create_buy_fak_order.py - FAK BUY orders
    9. 06_retry_handling.py - Custom retry logic
    10. 07_auto_retry_second_sample.py - Auto-retry patterns with RetryableClient
    11. 08_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_only usage
    • 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_headers parameter to HttpClient
  • Global and per-request header configuration
  • RetryableClient for 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)

Source distribution for limitless-sdk 1.2.0
File Size Uploaded
limitless_sdk-1.2.0.tar.gz 139.1 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for limitless-sdk 1.2.0
File Interpreter ABI Platform
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

Release history Release notifications | RSS feed

This release

1.2.0 This release

2 release files

1.1.1

2 release files

1.1.0

2 release files

1.0.12

2 release files

1.0.11

2 release files

1.0.10

2 release files

1.0.9

2 release files

1.0.8

2 release files

1.0.7

2 release files

1.0.6

2 release files

1.0.5

2 release files

1.0.4

2 release files

1.0.3

2 release files

1.0.2

2 release files

1.0.1

2 release files

1.0.0

2 release files

0.4.1

2 release files

0.4.0

2 release files

0.3.0

2 release files

0.2.0

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page