Skip to main content

Overview

This guide walks you through a complete Python implementation for trading on Limitless Exchange: authentication, fetching market data, building and signing orders with EIP-712, and submitting them via the REST API.

Prerequisites

1

Install dependencies

Install the required packages:
pip install eth-account requests web3
  • eth-account: EIP-712 signing and key management
  • requests: HTTP client for the REST API
  • web3: Address checksumming and utilities
2

Obtain credentials

  • Scoped API Token: Derive a token at limitless.exchange → profile menu → API Tokens → Derive. You get a token ID and a secret (base64-encoded). The secret is shown once — store it securely. See Authentication for the full flow.
  • Private Key: Your wallet’s private key for EIP-712 order signing. Never share or commit it.
Never share your private key. Store it in environment variables or a secure secrets manager. Do not hardcode it in source code or commit it to version control.

Authentication

Authenticated API requests (e.g. submitting orders) are signed with HMAC-SHA256 using your scoped API token. Each request carries three headers — lmts-api-key, lmts-timestamp, and lmts-signature — computed over a canonical message. See Authentication for the full reference.
Your private key is used only for EIP-712 order signing. The scoped API token (token ID + secret) handles request authentication. Both are required for trading. Public market data (browsing markets, orderbooks) needs no authentication.
import os
import hmac
import hashlib
import base64
from datetime import datetime, timezone

API_BASE = "https://api.limitless.exchange"
TOKEN_ID = os.environ["LMTS_TOKEN_ID"]       # token ID from token derivation
TOKEN_SECRET = os.environ["LMTS_TOKEN_SECRET"]  # base64-encoded secret


def sign_request(token_id: str, secret: str, method: str, path: str, body: str = "") -> dict:
    """Build HMAC auth headers for a single request (method + path + body)."""
    timestamp = datetime.now(timezone.utc).isoformat()
    message = f"{timestamp}\n{method}\n{path}\n{body}"
    signature = base64.b64encode(
        hmac.new(base64.b64decode(secret), message.encode("utf-8"), hashlib.sha256).digest()
    ).decode("utf-8")
    return {"lmts-api-key": token_id, "lmts-timestamp": timestamp, "lmts-signature": signature}
The signed path must include the query string if present. The timestamp must be within 30 seconds of server time. For GET requests, the body is an empty string.

Fetching Market Data

Use GET /markets/:slug to retrieve market details, including venue addresses and position IDs. Cache this data per market; it is static.
def fetch_market(slug: str) -> dict:
    """Fetch market data including venue and position IDs. Public endpoint — no auth."""
    resp = requests.get(f"{API_BASE}/markets/{slug}")
    resp.raise_for_status()
    return resp.json()
tokens.yes is the YES token ID, tokens.no is the NO token ID. Use the appropriate one based on your order side and outcome.

Building Order Payloads

Orders require specific fields. Key values:
FieldValueDescription
side0BUY
side1SELL
signatureType0EOA wallet
orderTypeGTCGood Till Cancelled
orderTypeFOKFill or Kill
USDC uses 6 decimals (1 USDC = 1,000,000 units). Shares are scaled by 1e6.
You pay USDC, receive shares. Use tokens.yes for YES, tokens.no for NO.
makerAmount = price_in_dollars * num_shares * 1e6   # USDC you pay
takerAmount = num_shares * 1e6                       # Shares you receive
side = 0
Example: BUY 10 YES shares at $0.65 → makerAmount = 6,500,000, takerAmount = 10,000,000

EIP-712 Signing

Sign orders using EIP-712 with the venue’s exchange address as verifyingContract.
See EIP-712 Order Signing for the full type definition and field reference.
from eth_account import Account
from eth_account.messages import encode_typed_data
from web3 import Web3

CHAIN_ID = 8453  # Base
ZERO_ADDRESS = "0x0000000000000000000000000000000000000000"

def sign_order(order_data: dict, verifying_contract: str, private_key: str) -> str:
    """Sign order with EIP-712 using venue.exchange as verifyingContract."""
    domain = {
        "name": "Limitless CTF Exchange",
        "version": "1",
        "chainId": CHAIN_ID,
        "verifyingContract": Web3.to_checksum_address(verifying_contract),
    }
    types = {
        "EIP712Domain": [
            {"name": "name", "type": "string"},
            {"name": "version", "type": "string"},
            {"name": "chainId", "type": "uint256"},
            {"name": "verifyingContract", "type": "address"},
        ],
        "Order": [
            {"name": "salt", "type": "uint256"},
            {"name": "maker", "type": "address"},
            {"name": "signer", "type": "address"},
            {"name": "taker", "type": "address"},
            {"name": "tokenId", "type": "uint256"},
            {"name": "makerAmount", "type": "uint256"},
            {"name": "takerAmount", "type": "uint256"},
            {"name": "expiration", "type": "uint256"},
            {"name": "nonce", "type": "uint256"},
            {"name": "feeRateBps", "type": "uint256"},
            {"name": "side", "type": "uint8"},
            {"name": "signatureType", "type": "uint8"},
        ],
    }
    message = {
        "salt": order_data["salt"],
        "maker": Web3.to_checksum_address(order_data["maker"]),
        "signer": Web3.to_checksum_address(order_data["signer"]),
        "taker": Web3.to_checksum_address(order_data["taker"]),
        "tokenId": int(order_data["tokenId"]),
        "makerAmount": order_data["makerAmount"],
        "takerAmount": order_data["takerAmount"],
        "expiration": order_data["expiration"],
        "nonce": order_data["nonce"],
        "feeRateBps": order_data["feeRateBps"],
        "side": order_data["side"],
        "signatureType": order_data["signatureType"],
    }
    typed_data = {
        "types": types,
        "primaryType": "Order",
        "domain": domain,
        "message": message,
    }
    encoded = encode_typed_data(typed_data)
    signed = Account.sign_message(encoded, private_key=private_key)
    return signed.signature.hex()
All addresses must be checksummed (EIP-55). Use Web3.to_checksum_address().

Submitting Orders

Send the signed order to POST /orders:
def submit_order(order_payload: dict) -> dict:
    """Submit a signed order to the API (HMAC-authenticated)."""
    body = json.dumps(order_payload)
    auth = sign_request(TOKEN_ID, TOKEN_SECRET, "POST", "/orders", body)
    resp = requests.post(
        f"{API_BASE}/orders",
        headers={**auth, "Content-Type": "application/json"},
        data=body,
    )
    resp.raise_for_status()
    return resp.json()

Complete Working Example

"""
Limitless Exchange - Python Quick Start
Place a BUY order for YES shares on a CLOB market.
"""
import os
import time
import json
import hmac
import hashlib
import base64
from datetime import datetime, timezone
import requests
from eth_account import Account
from eth_account.messages import encode_typed_data
from web3 import Web3

API_BASE = "https://api.limitless.exchange"
CHAIN_ID = 8453
ZERO_ADDRESS = "0x0000000000000000000000000000000000000000"

# Load from environment
TOKEN_ID = os.environ["LMTS_TOKEN_ID"]
TOKEN_SECRET = os.environ["LMTS_TOKEN_SECRET"]  # base64-encoded
PRIVATE_KEY = os.environ["PRIVATE_KEY"]
OWNER_ID = int(os.environ.get("OWNER_ID", "0"))  # Profile ID from API


def sign_request(token_id: str, secret: str, method: str, path: str, body: str = "") -> dict:
    timestamp = datetime.now(timezone.utc).isoformat()
    message = f"{timestamp}\n{method}\n{path}\n{body}"
    signature = base64.b64encode(
        hmac.new(base64.b64decode(secret), message.encode("utf-8"), hashlib.sha256).digest()
    ).decode("utf-8")
    return {"lmts-api-key": token_id, "lmts-timestamp": timestamp, "lmts-signature": signature}


def fetch_market(slug: str) -> dict:
    # Public endpoint — no auth required.
    resp = requests.get(f"{API_BASE}/markets/{slug}")
    resp.raise_for_status()
    return resp.json()


def sign_order(order_data: dict, verifying_contract: str, pk: str) -> str:
    domain = {
        "name": "Limitless CTF Exchange",
        "version": "1",
        "chainId": CHAIN_ID,
        "verifyingContract": Web3.to_checksum_address(verifying_contract),
    }
    types = {
        "EIP712Domain": [
            {"name": "name", "type": "string"},
            {"name": "version", "type": "string"},
            {"name": "chainId", "type": "uint256"},
            {"name": "verifyingContract", "type": "address"},
        ],
        "Order": [
            {"name": "salt", "type": "uint256"},
            {"name": "maker", "type": "address"},
            {"name": "signer", "type": "address"},
            {"name": "taker", "type": "address"},
            {"name": "tokenId", "type": "uint256"},
            {"name": "makerAmount", "type": "uint256"},
            {"name": "takerAmount", "type": "uint256"},
            {"name": "expiration", "type": "uint256"},
            {"name": "nonce", "type": "uint256"},
            {"name": "feeRateBps", "type": "uint256"},
            {"name": "side", "type": "uint8"},
            {"name": "signatureType", "type": "uint8"},
        ],
    }
    msg = {
        "salt": order_data["salt"],
        "maker": Web3.to_checksum_address(order_data["maker"]),
        "signer": Web3.to_checksum_address(order_data["signer"]),
        "taker": Web3.to_checksum_address(order_data["taker"]),
        "tokenId": int(order_data["tokenId"]),
        "makerAmount": order_data["makerAmount"],
        "takerAmount": order_data["takerAmount"],
        "expiration": order_data["expiration"],
        "nonce": order_data["nonce"],
        "feeRateBps": order_data["feeRateBps"],
        "side": order_data["side"],
        "signatureType": order_data["signatureType"],
    }
    encoded = encode_typed_data({
        "types": types,
        "primaryType": "Order",
        "domain": domain,
        "message": msg,
    })
    signed = Account.sign_message(encoded, private_key=pk)
    return signed.signature.hex()


def place_buy_order(
    market_slug: str,
    num_shares: float,
    price_per_share: float,
    order_type: str = "GTC",
) -> dict:
    market = fetch_market(market_slug)
    venue = market["venue"]
    tokens = market["tokens"]
    account = Account.from_key(PRIVATE_KEY)
    maker = account.address

    # YES token is tokens["yes"], NO token is tokens["no"]
    token_id = tokens["yes"]
    maker_amount = int(price_per_share * num_shares * 1e6)
    taker_amount = int(num_shares * 1e6)

    salt = int(time.time() * 1000)
    order_data = {
        "salt": salt,
        "maker": Web3.to_checksum_address(maker),
        "signer": Web3.to_checksum_address(maker),
        "taker": ZERO_ADDRESS,
        "tokenId": token_id,
        "makerAmount": maker_amount,
        "takerAmount": taker_amount,
        "expiration": 0,
        "nonce": 0,
        "feeRateBps": 0,
        "side": 0,  # BUY
        "signatureType": 0,  # EOA
    }
    signature = sign_order(order_data, venue["exchange"], PRIVATE_KEY)

    order_payload = {
        "order": {**order_data, "signature": signature},
        "ownerId": OWNER_ID,
        "orderType": order_type,
        "marketSlug": market_slug,
    }
    body = json.dumps(order_payload)
    auth = sign_request(TOKEN_ID, TOKEN_SECRET, "POST", "/orders", body)
    resp = requests.post(
        f"{API_BASE}/orders",
        headers={**auth, "Content-Type": "application/json"},
        data=body,
    )
    resp.raise_for_status()
    return resp.json()


if __name__ == "__main__":
    result = place_buy_order(
        market_slug="your-market-slug",
        num_shares=10.0,
        price_per_share=0.65,
        order_type="GTC",
    )
    print(result)
ownerId is your profile ID. Obtain it from your first authenticated response (e.g. portfolio or auth endpoints) or from the Limitless UI. Set OWNER_ID in your environment.

Troubleshooting

1

401 Unauthorized

  • Verify LMTS_TOKEN_ID and LMTS_TOKEN_SECRET are set and the token is active in the Limitless UI.
  • Ensure lmts-timestamp is within 30 seconds of server time (check your clock).
  • Confirm the signed path matches the request path including any query string, and the signed body is the exact bytes you send.
2

400 Invalid order / signature mismatch

  • Verify verifyingContract is the market’s venue.exchange address.
  • Ensure all addresses are checksummed (EIP-55).
  • Confirm makerAmount and takerAmount use 1e6 scaling for USDC and shares.
3

Insufficient balance or allowance

  • Ensure you have enough USDC on Base for BUY orders.
  • Approve USDC to venue.exchange for BUY; Conditional Tokens to venue.exchange (and venue.adapter for NegRisk SELL) for SELL.
4

Wrong tokenId

Use tokens.yes for YES and tokens.no for NO. Double-check you are trading the correct outcome.

Next Steps

EIP-712 Signing

Full order type definition and field reference.

Venue System

Understanding venue contracts and token approvals.

API Reference

Complete endpoint documentation.

Authentication

Scoped API token setup and HMAC request signing.