Prediction Market API Guide: Polymarket, Kalshi & PredictIt Integration 2025

Prediction market APIs enable automated trading, real-time data access, portfolio management, and algorithmic strategies unavailable through platform user interfaces. This comprehensive developer guide covers API integration for Polymarket, Kalshi, and PredictIt—including authentication methods, endpoint documentation, rate limiting, WebSocket connections, error handling, and practical code examples. Whether you’re building a trading bot, analyzing market efficiency, or developing a prediction market aggregator, this guide provides the technical foundation for API-driven prediction market interaction.

From REST API endpoints returning market odds to WebSocket feeds delivering millisecond-latency updates, prediction market infrastructure has evolved to support institutional-grade automated trading since 2020.

Why Use Prediction Market APIs

Automated Trading Execution: Manual trading requires constant monitoring—watching Fed announcements, tracking election results, following sports scores. APIs enable automated trading strategies based on predetermined rules, executing trades 24/7 without human intervention.

Real-Time Data Access: Platform UIs refresh every few seconds; WebSocket APIs deliver updates in milliseconds. Speed advantage crucial for capturing arbitrage opportunities and reacting to breaking news before broader market adjusts.

Portfolio Management at Scale: Managing 50-100+ simultaneous positions across categories manually becomes impossible. APIs enable programmatic position tracking, risk analysis, profit/loss calculation, and automated rebalancing.

Backtesting Strategies: Historical data via APIs allows strategy validation before risking capital. Test whether contrarian political prediction markets betting or momentum sports trading generated positive returns 2020-2024.

Cross-Platform Aggregation: Compare odds across Polymarket, Kalshi, PredictIt simultaneously. Identify arbitrage opportunities, find best prices, or build unified prediction market dashboards aggregating all platforms.

Research & Analysis: Academics studying market efficiency, journalists tracking political odds, analysts researching crowd wisdom—all require structured data unavailable through standard platform interfaces.

Polymarket API: Decentralized Market Access

API Overview

Base URL: https://clob.polymarket.com (CLOB = Central Limit Order Book)
Documentation: docs.polymarket.com (official), polymarket-api.notion.site (community)
Authentication: API keys generated in dashboard + HMAC-SHA256 signing
Rate Limits: 100 req/min (public), 300 req/min (authenticated)

Architecture: Polymarket operates on Polygon L2 blockchain with off-chain CLOB for performance. API interacts with CLOB for trading, queries blockchain for settlement data.

Authentication Setup

Generate API Key:

  1. Login to Polymarket dashboard
  2. Navigate to Settings → API Keys
  3. Generate new key (receives api_key and api_secret)
  4. Store securely—secrets not retrievable after initial display

Request Signing (Python):

import hmac
import hashlib
import time

def sign_request(secret, timestamp, method, path, body=""):
    message = f"{timestamp}{method}{path}{body}"
    signature = hmac.new(
        secret.encode(),
        message.encode(),
        hashlib.sha256
    ).hexdigest()
    return signature

# Usage
api_secret = "your_secret_here"
timestamp = str(int(time.time() * 1000))
method = "GET"
path = "/markets"

signature = sign_request(api_secret, timestamp, method, path)

Headers:

headers = {
    "POLY-API-KEY": "your_api_key",
    "POLY-TIMESTAMP": timestamp,
    "POLY-SIGNATURE": signature
}

Core Endpoints

GET /markets – List All Markets

import requests

response = requests.get("https://clob.polymarket.com/markets")
markets = response.json()

for market in markets[:5]:  # First 5 markets
    print(f"{market['question']}: YES ${market['outcomePrices'][0]}")

Response Example:

{
  "id": "0x1234...abcd",
  "question": "Will Bitcoin reach $150,000 by Dec 31, 2025?",
  "outcomes": ["YES", "NO"],
  "outcomePrices": ["0.38", "0.62"],
  "volume": "8240000",
  "liquidity": "2100000",
  "endDate": "2025-12-31T23:59:59Z"
}

GET /orderbook/{market_id} – Order Book Depth

Returns full order book with bids/asks at each price level:

market_id = "0x1234...abcd"
response = requests.get(f"https://clob.polymarket.com/orderbook/{market_id}")
orderbook = response.json()

print("BIDS (buy orders):")
for bid in orderbook['bids'][:5]:
    print(f"  ${bid['price']} - {bid['size']} shares")

print("ASKS (sell orders):")  
for ask in orderbook['asks'][:5]:
    print(f"  ${ask['price']} - {ask['size']} shares")

POST /orders – Place Order

Requires authentication:

order = {
    "market": "0x1234...abcd",
    "outcome": "YES",
    "side": "BUY",
    "type": "LIMIT",
    "price": "0.38",
    "size": "1000"
}

headers = {
    "POLY-API-KEY": api_key,
    "POLY-TIMESTAMP": timestamp,
    "POLY-SIGNATURE": signature
}

response = requests.post(
    "https://clob.polymarket.com/orders",
    json=order,
    headers=headers
)

if response.status_code == 200:
    print(f"Order placed: {response.json()['orderId']}")

GET /positions – Current Positions

Returns all user positions with PnL:

response = requests.get(
    "https://clob.polymarket.com/positions",
    headers=headers
)

for position in response.json():
    print(f"{position['market']}: {position['outcome']} "
          f"x{position['size']} @ ${position['avgPrice']} "
          f"(PnL: ${position['unrealizedPnL']})")

WebSocket Real-Time Feed

Connection:

import websocket
import json

def on_message(ws, message):
    data = json.loads(message)
    print(f"Update: {data['market']} - YES: ${data['yesPrice']}")

def on_error(ws, error):
    print(f"Error: {error}")

ws = websocket.WebSocketApp(
    "wss://clob.polymarket.com/ws",
    on_message=on_message,
    on_error=on_error
)

# Subscribe to specific market
ws.send(json.dumps({
    "type": "subscribe",
    "market": "0x1234...abcd"
}))

ws.run_forever()

Latency: <50ms for price updates—critical for arbitrage bots

Rate Limiting & Best Practices

Limits:

  • Public endpoints: 100 requests/minute
  • Authenticated: 300 requests/minute
  • WebSocket: Unlimited (but throttle subscriptions to <100 markets)

Handling Rate Limits:

import time

def rate_limited_request(url, max_retries=3):
    for attempt in range(max_retries):
        response = requests.get(url)

        if response.status_code == 429:  # Rate limited
            retry_after = int(response.headers.get('Retry-After', 60))
            print(f"Rate limited, waiting {retry_after}s")
            time.sleep(retry_after)
            continue

        return response

    raise Exception("Max retries exceeded")

Kalshi API: CFTC-Regulated Market Access

API Overview

Base URL: https://api.kalshi.com/v1
Sandbox: https://demo-api.kalshi.com/v1
Documentation: docs.kalshi.com/api (comprehensive official docs)
Authentication: OAuth 2.0
Rate Limits: 300 req/min (standard), 1,000 req/min (institutional)

Sandbox Environment: Test all functionality with fake money—mirrors production data with 5-minute delay. Perfect for development without risking capital.

OAuth 2.0 Authentication

Initial Token:

import requests

auth_response = requests.post(
    "https://api.kalshi.com/v1/oauth/token",
    json={
        "email": "user@example.com",
        "password": "your_password"
    }
)

token = auth_response.json()['access_token']
refresh_token = auth_response.json()['refresh_token']

Using Token:

headers = {
    "Authorization": f"Bearer {token}"
}

response = requests.get(
    "https://api.kalshi.com/v1/markets",
    headers=headers
)

Refresh Token (expires after 7 days):

refresh_response = requests.post(
    "https://api.kalshi.com/v1/oauth/refresh",
    json={"refresh_token": refresh_token}
)

new_token = refresh_response.json()['access_token']

Core Endpoints

GET /events – All Events

Returns events (Fed decisions, elections, sports):

response = requests.get(
    "https://api.kalshi.com/v1/events",
    headers=headers
)

for event in response.json()['events']:
    print(f"{event['title']} - {event['category']}")
    print(f"  Markets: {event['market_count']}")

GET /markets – Market Details

params = {
    "event_ticker": "FED-25DEC",  # Fed December decision
    "status": "open"
}

response = requests.get(
    "https://api.kalshi.com/v1/markets",
    params=params,
    headers=headers
)

for market in response.json()['markets']:
    print(f"{market['title']}")
    print(f"  YES: ${market['yes_bid']} - ${market['yes_ask']}")
    print(f"  Volume: ${market['volume']}")

POST /orders – Place Order

order = {
    "market_ticker": "FED-25DEC-T0.25",
    "side": "YES",
    "action": "BUY",
    "count": 100,  # 100 contracts
    "type": "LIMIT",
    "yes_price": 82  # Price in cents ($0.82)
}

response = requests.post(
    "https://api.kalshi.com/v1/orders",
    json=order,
    headers=headers
)

print(f"Order ID: {response.json()['order_id']}")

GET /portfolio/positions – Current Positions

response = requests.get(
    "https://api.kalshi.com/v1/portfolio/positions",
    headers=headers
)

total_pnl = 0
for position in response.json()['positions']:
    pnl = position['realized_pnl'] + position['unrealized_pnl']
    total_pnl += pnl
    print(f"{position['market_ticker']}: {position['position']} "
          f"contracts (PnL: ${pnl/100:.2f})")

print(f"\nTotal PnL: ${total_pnl/100:.2f}")

Advanced Order Types

Stop-Limit Orders:

order = {
    "market_ticker": "BTC-150K-31DEC",
    "side": "YES",
    "action": "SELL",
    "count": 50,
    "type": "STOP_LIMIT",
    "stop_price": 35,  # Trigger at $0.35
    "yes_price": 33    # Execute at $0.33
}

Fill-or-Kill (FOK):

order = {
    "market_ticker": "NFL-SF-SB60",
    "side": "YES",
    "action": "BUY",
    "count": 500,
    "type": "LIMIT",
    "yes_price": 22,
    "time_in_force": "FOK"  # Fill entire order or cancel
}

Sandbox Testing

Switch to Sandbox:

KALSHI_BASE = "https://demo-api.kalshi.com/v1"  # Sandbox
# KALSHI_BASE = "https://api.kalshi.com/v1"  # Production

# All API calls identical, just change base URL
response = requests.get(f"{KALSHI_BASE}/markets", headers=headers)

Benefits:

  • Test strategies risk-free
  • Verify API integration before production
  • Experiment with order types and edge cases
  • Production-like data (5-minute delay)

PredictIt API: Read-Only Market Data

API Overview

Base URL: https://www.predictit.org/api/marketdata
Authentication: None required (public read-only)
Rate Limits: 1 request/second (strictly enforced)
Documentation: predictit.freshdesk.com/support/solutions/articles/12000001878

Limitations: No trading via API, no WebSocket, no historical data (use CSV downloads instead)

Available Endpoints

GET /all – All Markets

import requests
import time

response = requests.get("https://www.predictit.org/api/marketdata/all")
data = response.json()

for market in data['markets'][:10]:
    print(f"{market['name']}")
    for contract in market['contracts']:
        print(f"  {contract['name']}: ${contract['lastTradePrice']}")

time.sleep(1)  # Respect 1 req/sec limit

GET /markets/{id} – Specific Market

market_id = 7053  # Example: 2028 GOP nominee market
response = requests.get(f"https://www.predictit.org/api/marketdata/markets/{market_id}")
market = response.json()

print(f"{market['name']}")
print(f"Status: {market['status']}")
for contract in market['contracts']:
    print(f"{contract['name']}: Buy ${contract['bestBuyYesCost']} | "
          f"Sell ${contract['bestSellYesCost']}")

Response Fields:

  • bestBuyYesCost – Current best ask (price to buy YES)
  • bestSellYesCost – Current best bid (price to sell YES)
  • lastTradePrice – Most recent executed trade
  • displayOrder – Ranking within market

Rate Limiting Compliance

Strict 1 req/sec Enforcement:

from time import time, sleep

class PredictItClient:
    def __init__(self):
        self.last_request = 0
        self.min_interval = 1.0  # 1 second

    def get(self, endpoint):
        # Enforce rate limit
        elapsed = time() - self.last_request
        if elapsed < self.min_interval:
            sleep(self.min_interval - elapsed)

        response = requests.get(f"https://www.predictit.org/api/marketdata{endpoint}")
        self.last_request = time()
        return response.json()

# Usage
client = PredictItClient()
data = client.get("/all")  # Automatically rate limited

Use Cases for Read-Only API

Price Monitoring:

# Track specific market, alert on price changes
target_price = 0.40
market_id = 7053

while True:
    data = client.get(f"/markets/{market_id}")
    current_price = data['contracts'][0]['lastTradePrice']

    if current_price <= target_price:
        send_alert(f"Price hit ${current_price}!")
        break

    sleep(60)  # Check every minute

Cross-Platform Comparison:

# Compare PredictIt vs Polymarket prices
predictit_price = get_predictit_price(market_id)
polymarket_price = get_polymarket_price(equivalent_id)

spread = abs(polymarket_price - predictit_price)
if spread > 0.05:  # 5 cent arbitrage opportunity
    print(f"Arbitrage: {spread*100:.1f}¢ spread")

Error Handling & Best Practices

Common Errors:

def safe_api_call(func, *args, **kwargs):
    try:
        return func(*args, **kwargs)
    except requests.exceptions.Timeout:
        print("Request timed out, retrying...")
        time.sleep(5)
        return safe_api_call(func, *args, **kwargs)
    except requests.exceptions.ConnectionError:
        print("Connection error, waiting...")
        time.sleep(30)
        return safe_api_call(func, *args, **kwargs)
    except requests.exceptions.HTTPError as e:
        if e.response.status_code == 401:
            print("Authentication failed, refresh token...")
            refresh_auth_token()
            return safe_api_call(func, *args, **kwargs)
        elif e.response.status_code == 429:
            print("Rate limited, backing off...")
            time.sleep(60)
            return safe_api_call(func, *args, **kwargs)
        else:
            raise

Logging for Debugging:

import logging

logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
    filename='prediction_market_api.log'
)

logger = logging.getLogger(__name__)

def place_order(market, side, price):
    logger.info(f"Placing order: {market} {side} @ ${price}")
    try:
        response = api.post_order(market, side, price)
        logger.info(f"Order success: {response['order_id']}")
        return response
    except Exception as e:
        logger.error(f"Order failed: {str(e)}")
        raise

Conclusion

Prediction market APIs transform manual trading into automated, systematic strategies. Polymarket’s WebSocket feeds enable millisecond-latency trading, Kalshi’s OAuth 2.0 and sandbox environment support institutional-grade development, and PredictIt’s read-only API facilitates research and price monitoring. Whether building arbitrage bots, portfolio managers, or research platforms, API integration unlocks capabilities unavailable through platform UIs.

Ready to explore more tools? Learn arbitrage strategies, understand binary market mechanics, explore smart contracts, or return to the complete tools guide. For platform comparisons, review Polymarket features, Kalshi details, or track live markets.