Polymarket runs a Central Limit Order Book (CLOB) — the same architecture as crypto exchanges. If you want to build a trading bot, you need to talk to this API directly. Here's how.
Setup
You need a Polymarket account with USDC on Polygon, an API key from your profile settings, and your Polygon wallet private key. Install the official wrapper:
pip install py-clob-client requests
Connecting the Client
import os
from py_clob_client.client import ClobClient
from py_clob_client.clob_types import ApiCreds
client = ClobClient(
"https://clob.polymarket.com",
key=os.environ["POLYMARKET_PRIVATE_KEY"],
chain_id=137, # Polygon mainnet
creds=ApiCreds(
api_key=os.environ["POLYMARKET_API_KEY"],
api_secret=os.environ["POLYMARKET_API_SECRET"],
api_passphrase=os.environ["POLYMARKET_API_PASSPHRASE"],
),
)
Always use environment variables for credentials. Never hardcode keys.
Finding Sports Markets
Every Polymarket question has a condition_id (the question) and token_id values for YES/NO outcomes. You need the token_id to trade:
import requests
resp = requests.get(
"https://gamma-api.polymarket.com/markets",
params={"tag": "sports", "active": True, "limit": 50}
)
for m in resp.json():
print(f"{m['question']} | ID: {m['condition_id']}")
Reading the Orderbook
The orderbook tells you exactly what prices and depth are available:
token_id = "YOUR_TOKEN_ID_HERE"
book = client.get_order_book(token_id)
best_bid = float(book.bids[0].price) if book.bids else 0
best_ask = float(book.asks[0].price) if book.asks else 1
midpoint = (best_bid + best_ask) / 2
spread = best_ask - best_bid
print(f"Bid: {best_bid} Ask: {best_ask} Mid: {midpoint:.4f} Spread: {spread:.2f}")
The spread is your execution cost. A 3-cent spread means you lose 3 cents just entering and exiting. This is why execution quality matters so much.
Placing a Limit Order
from py_clob_client.order_builder.constants import BUY
signed_order = client.create_order({
"token_id": token_id,
"price": 0.63, # Limit price in USDC
"size": 50, # Number of contracts
"side": BUY,
})
response = client.post_order(signed_order)
print(f"Order placed: {response}")
Orders sit on the book until filled, canceled, or the market resolves. You pay ~2% fees on fills.
Managing Orders
# Check open orders
for order in client.get_orders():
print(f"{order['side']} {order['original_size']} @ {order['price']} "
f"filled: {order['size_matched']}")
# Cancel a single order
client.cancel(order_id="YOUR_ORDER_ID")
# Cancel everything
client.cancel_all()
Polling Loop for a Trading Bot
import time
while True:
book = client.get_order_book(token_id)
best_ask = float(book.asks[0].price) if book.asks else 1
spread = float(book.asks[0].price) - float(book.bids[0].price) if book.bids else 1
model_fair = your_model.predict(game_state)
edge = model_fair - best_ask
if edge > 0.08 and spread < 0.04:
# Signal found — place buy order
pass
time.sleep(5)
The 5-second interval balances rate limits against signal freshness. Most tradable windows last 15-60 seconds, so 5-second polling catches the majority.
Common Pitfalls
Rate limits: Space your calls. Use exponential backoff on 429 responses.
Stale orderbooks: During score changes, market makers pull quotes. Always check quote freshness before trading.
Token ID confusion: Each YES/NO outcome has a separate token ID. Buying the wrong side is an expensive mistake.
From API to Bot
Connecting to the API is step one. A profitable bot also needs a WP model that disagrees with the market, an edge filter that rejects bad signals, and execution logic that respects market microstructure. Our course covers all of these.
Part of the ZenHodl blog. We write about sports analytics, prediction markets, and building trading bots with Python.