pmxt is the CCXT for prediction markets: one Python SDK to read data and trade across Polymarket, Kalshi, Limitless, Probable, and Baozi. Install with pip install pmxt, initialize an exchange, and use the same method signatures regardless of platform. With Dome API absorbed into Polymarket, pmxt is the only independent unified prediction market SDK still standing.

Why pmxt Exists

Prediction markets are fragmented. Polymarket uses EIP-712 signatures on Polygon. Kalshi uses RSA key authentication over REST. Limitless uses its own EIP-712 variant. Each platform has different data formats, different price conventions, and different SDKs.

If you’re building an agent that needs to scan markets across multiple exchanges — for arbitrage, for intelligence gathering, for portfolio management — you’d normally maintain three separate API integrations. pmxt eliminates that. One pip install, one set of method names, every exchange.

The timing matters: Polymarket acquired Dome (the YC W25 unified API startup) in February 2026. Dome raised $5.2M and had 50+ developers building on it, but its standalone API is now being folded into Polymarket’s internal tooling. That leaves pmxt as the only open-source, exchange-neutral prediction market SDK available. If you were on Dome, you need to migrate. If you’re starting fresh, pmxt is the default choice.

For context on how pmxt fits into the full autonomous agent architecture, see The Agent Betting Stack Explained. pmxt operates at Layer 3 — Trading, handling market execution while other layers handle identity, wallet management, and intelligence.


Prerequisites

pmxt has an unusual dependency: it requires both Python and Node.js.

The library uses a sidecar architecture — a local Node.js server runs on port 3847 and handles all exchange communication. The Python SDK is a wrapper that sends requests to this sidecar over HTTP. This means:

  • Python 3.8+ — for the SDK itself
  • Node.js 18+ — must be installed and the node command available on your PATH
  • pip — standard Python package manager

Verify Node.js is accessible:

node --version
# v20.x.x or higher

If you don’t have Node.js, install it from nodejs.org or via your package manager:

# macOS
brew install node

# Ubuntu/Debian
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt-get install -y nodejs

# Windows
winget install OpenJS.NodeJS

Installation

pip install pmxt

That’s it. The first time you instantiate an exchange, pmxt automatically downloads and starts the sidecar server. You don’t need to install anything via npm separately — the Python package handles the Node.js dependency internally.

Verify the installation:

import pmxt

api = pmxt.Exchange()
events = api.fetch_events(query='Fed')
print(f"Found {len(events)} events")

If this prints a count without errors, you’re set. If you see a connection error, check that node is on your PATH.


Core Concepts: Events, Markets, Outcomes

pmxt normalizes prediction market data into a three-level hierarchy that’s consistent across all exchanges:

┌─────────────────────────────────────────────────────────┐
│  Event                                                  │
│  "Who will Trump nominate as Fed Chair?"                │
│                                                         │
│  ┌─────────────────────┐  ┌─────────────────────┐      │
│  │  Market              │  │  Market              │     │
│  │  "Kevin Warsh?"      │  │  "Kevin Hassett?"    │     │
│  │                      │  │                      │     │
│  │  ┌────┐  ┌────┐     │  │  ┌────┐  ┌────┐     │     │
│  │  │ Yes│  │ No │     │  │  │ Yes│  │ No │     │     │
│  │  │0.45│  │0.55│     │  │  │0.20│  │0.80│     │     │
│  │  └────┘  └────┘     │  │  └────┘  └────┘     │     │
│  │  (Outcomes)          │  │  (Outcomes)          │     │
│  └─────────────────────┘  └─────────────────────┘      │
└─────────────────────────────────────────────────────────┘
  • Event — The broad topic. Contains one or more markets.
  • Market — A specific tradeable question within an event.
  • Outcome — The actual share you buy or sell (Yes / No). Each outcome has an outcome_id that you pass to trading and data methods.

This distinction matters because different exchanges use different terminology. Polymarket calls them “conditions” and “tokens.” Kalshi calls them “events” and “markets.” pmxt unifies everything into this Event → Market → Outcome hierarchy.


Reading Market Data (No Auth Required)

All read operations work without credentials. Use pmxt.Exchange() for exchange-agnostic browsing, or pmxt.Polymarket() / pmxt.Kalshi() to target a specific exchange.

Search Events

import pmxt

api = pmxt.Exchange()

# Search by keyword
events = api.fetch_events(query='Federal Reserve')

for event in events[:5]:
    print(f"{event.title}")
    print(f"  Markets: {len(event.markets)}")
    for market in event.markets[:3]:
        print(f"    {market.title}: Yes={market.yes.price:.2f} No={market.no.price:.2f}")
    print()

Search Markets Directly

# Search across all markets
markets = api.fetch_markets(query='Trump', limit=10)

for m in markets:
    print(f"{m.title}")
    print(f"  Yes: {m.yes.price:.2f}  |  24h Volume: ${m.volume_24h:,.0f}")

Filter Markets

pmxt includes built-in filtering with text search, structured criteria, and custom predicates:

# Structured filter: high-volume markets with low yes price
undervalued = api.filter_markets(markets, {
    'volume_24h': {'min': 10000},
    'price': {'outcome': 'yes', 'max': 0.3}
})

# Custom predicate: markets with big price swings
volatile = api.filter_markets(markets, 
    lambda m: abs(m.yes.price_change_24h) > 0.05
)

Fetch a Single Market by Slug

market = api.fetch_market(slug='will-trump-win')
print(f"{market.title}: {market.yes.price:.2f}")

Price History and OHLCV Data

pmxt provides candlestick data at multiple resolutions — essential for backtesting and technical analysis.

import pmxt

poly = pmxt.Polymarket()

# Find a market
markets = poly.fetch_markets(query='Bitcoin price')
outcome_id = markets[0].yes.outcome_id  # CRITICAL: use outcome_id, not market_id

# Fetch hourly candles
candles = poly.fetch_ohlcv(outcome_id, resolution='1h', limit=100)

for candle in candles[-5:]:
    print(f"  Open: {candle.open:.3f}  High: {candle.high:.3f}  "
          f"Low: {candle.low:.3f}  Close: {candle.close:.3f}  "
          f"Vol: {candle.volume:.0f}")

Available resolutions: '1m', '5m', '15m', '1h', '6h', '1d'.

Common mistake: Passing the market ID instead of the outcome ID. On Polymarket, outcome_id maps to the CLOB Token ID. On Kalshi, it maps to the Market Ticker. Always use outcome.outcome_id.


Order Book Analysis

The order book reveals real liquidity — what you’ll actually pay when you trade.

import pmxt

poly = pmxt.Polymarket()

markets = poly.fetch_markets(query='Fed rate decision')
outcome = markets[0].yes

# Fetch current order book
book = poly.fetch_order_book(outcome.outcome_id)

print(f"Best bid: {book.bids[0].price:.3f} ({book.bids[0].size} contracts)")
print(f"Best ask: {book.asks[0].price:.3f} ({book.asks[0].size} contracts)")
spread = (book.asks[0].price - book.bids[0].price) * 100
print(f"Spread: {spread:.1f}%")

# Top 5 levels
print("\nBids:")
for level in book.bids[:5]:
    print(f"  {level.price:.3f} x {level.size}")

print("\nAsks:")
for level in book.asks[:5]:
    print(f"  {level.price:.3f} x {level.size}")

Simulate Execution Price

Before placing a large order, check what you’d actually pay given current liquidity:

# How much would 100 contracts cost?
avg_price = poly.get_execution_price(book, side='buy', amount=100)
print(f"Average fill price for 100 contracts: {avg_price:.4f}")

# Detailed execution with partial fill info
result = poly.get_execution_price_detailed(book, side='buy', amount=100)
print(f"Price: {result.price:.4f}")
print(f"Filled: {result.filled_amount}/{100}")
print(f"Fully filled: {result.fully_filled}")

This is critical for agent trading — you don’t want to assume best-ask pricing on a 1,000-contract order.


Trading: Setup and Order Placement

Trading requires exchange-specific credentials. Each exchange has its own authentication mechanism, but pmxt normalizes the trading interface.

Polymarket Authentication

Polymarket uses an Ethereum wallet private key. You’ll need your wallet’s private key and optionally a proxy address for proxy-wallet trading.

import pmxt
import os

exchange = pmxt.Polymarket(
    private_key=os.getenv('POLYMARKET_PRIVATE_KEY'),
    proxy_address=os.getenv('POLYMARKET_PROXY_ADDRESS'),  # Optional
    signature_type='gnosis-safe'  # Default
)

For a full guide on Polymarket authentication including wallet types and signature types, see the Polymarket API Tutorial.

Kalshi Authentication

Kalshi uses RSA key-pair authentication — simpler than Polymarket’s wallet-based system.

exchange = pmxt.Kalshi(
    api_key=os.getenv('KALSHI_API_KEY'),
    private_key=os.getenv('KALSHI_PRIVATE_KEY')  # RSA private key
)

For Kalshi-specific setup and API key generation, see the Kalshi API Guide.

Limitless Authentication

exchange = pmxt.Limitless(
    api_key=os.getenv('LIMITLESS_API_KEY'),
    private_key=os.getenv('LIMITLESS_PRIVATE_KEY')  # EIP-712 signing key
)

Check Balance

balance = exchange.fetch_balance()
print(f"Available: ${balance[0].available:.2f}")

Place a Limit Order

markets = exchange.fetch_markets(query='Trump')
market = markets[0]

order = exchange.create_order(
    outcome=market.yes,
    side='buy',
    type='limit',
    price=0.33,
    amount=100  # 100 contracts
)

print(f"Order ID: {order.id}")
print(f"Status: {order.status}")

Place a Market Order

order = exchange.create_order(
    outcome=market.yes,
    side='buy',
    type='market',
    amount=50
)

Check Order Status

order = exchange.fetch_order(order.id)
print(f"Status: {order.status}")
print(f"Filled: {order.filled}/{order.amount}")

Cancel an Order

cancelled = exchange.cancel_order(order.id)
print(f"Cancelled: {cancelled.status}")

View Open Orders

open_orders = exchange.fetch_open_orders()
for o in open_orders:
    print(f"  {o.side} {o.amount} @ {o.price}")

Check Positions

positions = exchange.fetch_positions()
for pos in positions:
    print(f"{pos.outcome_label}: {pos.size} shares @ ${pos.entry_price:.3f}")
    print(f"  Unrealized P&L: ${pos.unrealized_pnl:.2f}")

WebSocket Streaming

For real-time applications — trading bots, dashboards, arbitrage scanners — polling is too slow. pmxt provides WebSocket streaming through watch_* methods.

Stream Order Book Updates

import pmxt

poly = pmxt.Polymarket()
markets = poly.fetch_markets(query='Fed Chair')
outcome = markets[0].yes

# Continuous order book stream
while True:
    book = poly.watch_order_book(outcome.outcome_id)
    if book.bids and book.asks:
        spread = book.asks[0].price - book.bids[0].price
        print(f"Bid: {book.bids[0].price:.3f} | Ask: {book.asks[0].price:.3f} | Spread: {spread:.4f}")

Stream Trades

while True:
    trades = poly.watch_trades(outcome.outcome_id)
    for trade in trades:
        print(f"{trade.side.upper()} {trade.amount} @ {trade.price:.3f}")

Stream Wallet Activity

while True:
    activity = poly.watch_address('0xYOUR_ADDRESS', ['trades', 'positions'])
    print(f"Trades: {len(activity.trades)}, Positions: {len(activity.positions)}")

Clean Up

Always close WebSocket connections when done:

poly.close()

Cross-Exchange Arbitrage Scanner

One of pmxt’s killer use cases: scanning the same market across Polymarket and Kalshi simultaneously for price discrepancies. This is the unified API in action.

import pmxt

poly = pmxt.Polymarket()
kalshi = pmxt.Kalshi()

# Fetch similar markets from both exchanges
poly_markets = poly.fetch_markets(query='Fed rate cut', limit=50)
kalshi_markets = kalshi.fetch_markets(query='Fed rate cut', limit=50)

# Simple price comparison (real implementation needs fuzzy matching)
print("Cross-Exchange Price Comparison")
print("=" * 60)

for pm in poly_markets[:5]:
    # Find matching Kalshi market by title similarity
    for km in kalshi_markets:
        if pm.title.lower()[:30] == km.title.lower()[:30]:
            diff = abs(pm.yes.price - km.yes.price)
            if diff > 0.02:  # >2% spread
                print(f"\n{pm.title}")
                print(f"  Polymarket YES: {pm.yes.price:.3f}")
                print(f"  Kalshi YES:     {km.yes.price:.3f}")
                print(f"  SPREAD:         {diff:.3f} ({diff*100:.1f}%)")

For a production-grade arbitrage system with fuzzy matching, risk-adjusted sizing, and execution logic, see the Cross-Market Arbitrage Guide.


Complete Trading Bot Example

Here’s a minimal but functional agent pattern — scan markets, evaluate opportunities, and place orders:

import pmxt
import os

def run_agent():
    exchange = pmxt.Polymarket(
        private_key=os.getenv('POLYMARKET_PRIVATE_KEY'),
        proxy_address=os.getenv('POLYMARKET_PROXY_ADDRESS')
    )

    # 1. Check available capital
    balance = exchange.fetch_balance()
    available = balance[0].available
    print(f"Available balance: ${available:.2f}")

    if available < 10:
        print("Insufficient balance")
        return

    # 2. Scan for opportunities
    markets = exchange.fetch_markets(query='2026', limit=100)
    
    # 3. Filter: high volume, low price (potential undervaluation)
    targets = exchange.filter_markets(markets, {
        'volume_24h': {'min': 5000},
        'price': {'outcome': 'yes', 'max': 0.25}
    })

    print(f"\nFound {len(targets)} potential targets")

    for market in targets[:3]:
        # 4. Check order book liquidity
        book = exchange.fetch_order_book(market.yes.outcome_id)
        
        if not book.asks:
            continue
            
        spread = book.asks[0].price - book.bids[0].price if book.bids else 1.0
        
        if spread > 0.05:
            print(f"  Skipping {market.title[:50]} — spread too wide ({spread:.3f})")
            continue

        # 5. Simulate execution
        exec_price = exchange.get_execution_price(book, 'buy', 20)
        
        print(f"\n  Target: {market.title[:60]}")
        print(f"  Yes price: {market.yes.price:.3f}")
        print(f"  Avg fill for 20 contracts: {exec_price:.4f}")
        print(f"  Spread: {spread:.4f}")

        # 6. Place order (conservative limit)
        order = exchange.create_order(
            outcome=market.yes,
            side='buy',
            type='limit',
            price=round(market.yes.price * 0.98, 2),  # 2% below market
            amount=20
        )
        print(f"  Order placed: {order.id} ({order.status})")

    # 7. Review positions
    positions = exchange.fetch_positions()
    print(f"\nActive positions: {len(positions)}")
    for pos in positions[:5]:
        pnl_symbol = '+' if pos.unrealized_pnl > 0 else ''
        print(f"  {pos.outcome_label}: {pnl_symbol}${pos.unrealized_pnl:.2f}")

    exchange.close()

if __name__ == '__main__':
    run_agent()

This is a starting point. A production agent needs error handling, retry logic, rate limiting, and risk management. For the full autonomous agent architecture, see The Agent Betting Stack Explained.


Migrating from Dome API

Polymarket acquired Dome in February 2026. The standalone Dome API is being sunset. If you have existing Dome code, pmxt provides an automatic migration tool:

# Automatically convert Dome API calls to pmxt
npx dome-to-pmxt ./src

This codemod handles the common patterns: import rewrites, method name changes, and authentication restructuring.

Key Differences from Dome

FeatureDome APIpmxt
HostingCloud-hosted (Dome servers)Local sidecar (your machine)
AuthSingle API key from DomePer-exchange credentials
Data ownershipDome proxied all dataDirect exchange connection
CostPaid tiers for high volumeFree and open-source (MIT)
Vendor riskAcquired → sunsettingIndependent, community-driven
ExchangesPolymarket, KalshiPolymarket, Kalshi, Limitless, Probable, Baozi

The fundamental architectural difference: Dome was a hosted proxy — all your API calls went through Dome’s servers. pmxt runs entirely on your infrastructure. No third-party server in the middle, no data routing through someone else’s cloud, no vendor lock-in.

For a detailed Dome vs pmxt comparison with benchmarks, see the Dome vs pmxt vs OddsPapi Comparison.


Historical Data Archive

pmxt maintains a free data archive at archive.pmxt.dev with hourly snapshots of prediction market order book and trade data in Parquet format. Useful for:

  • Backtesting trading strategies
  • Training ML models on prediction market data
  • Research and academic analysis

The archive covers Polymarket, Kalshi, and Opinion data.


Sidecar Architecture: What You Need to Know

pmxt’s sidecar pattern is worth understanding because it affects deployment:

┌────────────────┐         HTTP/localhost:3847         ┌─────────────────┐
│  Your Python   │ ──────────────────────────────────> │  pmxt Sidecar   │
│  Application   │ <────────────────────────────────── │  (Node.js)      │
└────────────────┘                                     │                 │
                                                       │  ┌───────────┐ │
                                                       │  │Polymarket │ │
                                                       │  │  Adapter  │ │
                                                       │  ├───────────┤ │
                                                       │  │  Kalshi   │ │
                                                       │  │  Adapter  │ │
                                                       │  ├───────────┤ │
                                                       │  │ Limitless │ │
                                                       │  │  Adapter  │ │
                                                       │  └───────────┘ │
                                                       └─────────────────┘

Implications for deployment:

  • Server environments need Node.js installed alongside Python
  • Docker containers should include both Python and Node.js runtimes
  • Port 3847 must be available (configurable if conflicted)
  • Memory — the sidecar process consumes additional RAM for caching
  • Health monitoring — you need to watch both your Python app and the sidecar

If the sidecar crashes or gets stuck, use the management methods:

import pmxt

# Restart the sidecar
pmxt.restart_server()

# Stop the sidecar (and clean up lock files)
pmxt.stop_server()

Python vs TypeScript SDK: Method Name Mapping

pmxt maintains both Python and TypeScript SDKs. Python uses snake_case, TypeScript uses camelCase. The methods are otherwise identical:

PythonTypeScriptPurpose
fetch_events()fetchEvents()Search events
fetch_markets()fetchMarkets()Search markets
fetch_market()fetchMarket()Single market lookup
fetch_ohlcv()fetchOHLCV()Candlestick data
fetch_order_book()fetchOrderBook()Order book depth
fetch_trades()fetchTrades()Trade history
create_order()createOrder()Place order
cancel_order()cancelOrder()Cancel order
fetch_order()fetchOrder()Order status
fetch_open_orders()fetchOpenOrders()All open orders
fetch_positions()fetchPositions()Current positions
fetch_balance()fetchBalance()Account balance
watch_order_book()watchOrderBook()Stream order book
watch_trades()watchTrades()Stream trades
outcome.outcome_idoutcome.outcomeIdOutcome identifier
market.market_idmarket.marketIdMarket identifier
filter_markets()filterMarkets()Filter/search

Supported Exchanges Reference

ExchangeRead DataTradingWebSocketAuth Method
PolymarketYesYesYesWallet private key + EIP-712
KalshiYesYesYesAPI key + RSA private key
LimitlessYesYesYesAPI key + EIP-712
ProbableYesYesExchange-specific
BaoziYesYesExchange-specific

Troubleshooting

“Connection refused” on startup — Node.js isn’t installed or isn’t on your PATH. Run node --version to check.

“Port 3847 already in use” — A previous sidecar instance didn’t shut down cleanly. Run pmxt.stop_server() or kill the orphaned Node process manually.

Empty results from fetch_markets() — Some exchanges only return active markets. Try broader queries or check if the exchange is accessible from your location (Polymarket is geoblocked in the US for non-KYC users on the global API).

“outcome_id not found” errors — You’re passing a market ID where an outcome ID is expected. Always use market.yes.outcome_id or market.no.outcome_id for data and trading methods.

Stale data — The sidecar caches market data. Call exchange.load_markets(reload=True) to force a fresh fetch.


Where pmxt Fits in the Agent Betting Stack

pmxt is a Layer 3 — Trading tool. It handles market execution — finding markets, analyzing prices, and placing orders. A complete autonomous betting agent needs all four layers:

LayerWhat It DoesTools
Layer 1 — IdentityAgent proves who it isMoltbook, SIWE, ENS
Layer 2 — WalletAgent holds and spends moneyCoinbase Agentic Wallets, Safe
Layer 3 — TradingAgent finds and executes betspmxt, Polymarket CLOB, Kalshi API
Layer 4 — IntelligenceAgent analyzes and decidesClaude, Polyseer, CrewAI

For the complete architecture, see The Agent Betting Stack Explained.


What’s Next