All posts
GuidesTrading BotsPolymarket APIPython

How to Build a Polymarket Trading Bot

Most Polymarket bot tutorials are built on SDKs that no longer exist. This is the current path end to end — what to install in 2026, how auth actually works, where the data gaps are, and the failure modes that kill bots in week one.

14 min read

A Polymarket bot is six pieces: discover markets, read prices, decide, place orders, manage positions, and stay alive. The first two are easy, the fourth is fiddly, and the sixth is where most bots actually die. This guide walks the whole arc with verified code, and is honest about the parts that are genuinely hard.

Step 0: Decide what your bot trades

Worth doing before you write a line, because it determines everything downstream. Polymarket markets fall into two families with almost nothing in common:

Event marketsCrypto Up/Down markets
ExamplesElections, sports, awardsbtc-updown-5m, eth-updown-15m
LifespanWeeks to months5 minutes to 24 hours
What moves priceNews, information, sentimentMicrostructure and order flow
Edge comes fromBeing better informedBeing faster or better priced
Trades per dayA handfulHundreds
Latency sensitivityLowHigh

If you are automating, you are almost certainly interested in the second family — high frequency, continuous, and mechanical enough to encode. Those are covered in depth in Up/Down markets explained.

Step 1: Install the current SDK

Both SDKs give you one client interface over all of Polymarket's APIs, typed models, consistent pagination and built-in signing — which is a large amount of fiddly work you do not want to reimplement.

pip install polymarket-client
# or: uv add polymarket-client / poetry add polymarket-client

There are four Python clients, and picking the right one matters more than it looks:

ClientPublic dataTradingRealtime
AsyncPublicClientYesNoYes
AsyncSecureClientYesYesYes
PublicClientYesNoNo
SecureClientYesYesNo
Realtime subscriptions are async-only — the sync clients do not implement subscribe(). Bots want the async variants; the sync ones are for scripts and notebooks.

Step 2: Find markets

Public market discovery needs no credentials at all. The SDK's paginator is consistent across every list method, so this pattern generalises.

import asyncio
from polymarket import AsyncPublicClient


async def main() -> None:
    async with AsyncPublicClient() as client:
        pages = client.list_markets(closed=False, page_size=50)

        # Iterate items directly when page boundaries do not matter.
        async for market in pages.iter_items():
            if "updown" in (market.slug or ""):
                print(market.slug, market.condition_id)


asyncio.run(main())

Two things to internalise here. First, page.next_cursor is an opaque cursor — store it verbatim if you want to resume a scan later, and do not try to parse or construct one. Second, /markets carries the tightest rate limit on the whole platform at 300 req / 10s, so cache market metadata rather than re-listing it in a hot loop. Full numbers in the rate limits reference.

Step 3: Authenticate

Trading needs a wallet. The Python SDK builds the signer from a local private key:

import os
from polymarket import AsyncSecureClient

client = await AsyncSecureClient.create(
    private_key=os.environ["POLYMARKET_PRIVATE_KEY"],
    wallet=os.environ["POLYMARKET_WALLET_ADDRESS"],
)

Underneath, Polymarket uses two auth layers: L1 is an EIP-712 wallet signature that proves you control the address and lets you create or derive API credentials; L2 signs each private request with those credentials using HMAC-SHA256. The SDK handles both, which is the main reason to use it.

Step 4: Get real-time prices

The most common architectural mistake in a first bot is polling /book on a timer. It burns your rate limit budget, adds latency, and scales badly. Subscribe instead — the SDK merges multiple feeds into one event stream.

from polymarket.streams import MarketSpec

async with await client.subscribe([MarketSpec(token_ids=[token_id])]) as stream:
    async for event in stream:
        # MarketBookEvent | MarketPriceChangeEvent | MarketLastTradePriceEvent
        # | MarketTickSizeChangeEvent | MarketBestBidAskEvent
        # | NewMarketEvent | MarketResolvedEvent
        handle(event)

If you connect to the raw WebSocket instead of using the SDK, you must send a PING every 10 seconds or the connection is dropped, and you need custom_feature_enabled: true to receive best_bid_ask, new_market and market_resolved events at all. Those and the reconnect patterns are covered in the WebSocket guide.

Step 5: Place an order

A market order is the simplest thing that works, and amount is denominated in pUSD you are willing to spend rather than in shares:

market = await client.get_market(slug="<market-slug>")
token_id = market.outcomes.yes.token_id
assert token_id is not None

response = await client.place_market_order(
    token_id=token_id,
    side="BUY",
    amount="10",          # spend up to 10 pUSD
)

if not response.ok:
    raise RuntimeError(response.message)

# Settlement is on-chain and asynchronous — this waits for it.
hashes = await client.wait_for_order_fill_settlement(response)

For resting limit orders, two rules will bite you before anything else does. Prices must sit on the market's tick grid — call GET /tick-size per market rather than assuming 0.01, because tick size varies and can tighten as a market matures. And orders below the market's minimum size are rejected outright. Both come back as 400s with specific messages, listed in the error reference.

Step 6: Know whether the strategy works

Everything above is plumbing, and plumbing is the easy half. The hard question is whether your strategy makes money — and this is where Polymarket bot building diverges sharply from crypto spot bot building.

There is no testnet. No sandbox, no demo API, no paper trading endpoint. The words do not appear anywhere in Polymarket's documentation. Your first real order is real money against real counterparties. See does Polymarket have a testnet for the four workarounds.

And there is no historical order book. Polymarket's API serves current state only. Once a market resolves, its book returns 404 and its price history returns 200 with an empty array — a successful response containing nothing:

TOK=<token_id_of_a_resolved_market>

curl -s "https://clob.polymarket.com/book?token_id=$TOK"
# {"error":"No orderbook exists for the requested token id"}   HTTP 404

curl -s "https://clob.polymarket.com/prices-history?market=$TOK&interval=max&fidelity=1"
# {"history":[]}                                                HTTP 200

So you cannot backtest from Polymarket. You need point-in-time snapshots that somebody recorded while the markets ran. That is what PolyTest provides for crypto Up/Down markets — 90M+ snapshots, 8 levels of book depth per side, sub-second timestamps, resolved markets retained:

import os, requests

BASE = "https://api.polytest.io/api/v1"
H = {"X-API-Key": os.environ["POLYTEST_KEY"]}

markets = requests.get(f"{BASE}/markets",
                       params={"coin": "btc", "market_type": "5m", "limit": 50},
                       headers=H).json()["markets"]

snaps = requests.get(f"{BASE}/markets/{markets[0]['id']}/snapshots",
                     params={"include_orderbook": "true"},
                     headers=H).json()["snapshots"]


def simulate_fill(levels, qty):
    """Walk the ladder. Mid-price is not a price you can trade at."""
    filled = cost = 0.0
    for lvl in levels:
        take = min(lvl["size"], qty - filled)
        cost += take * lvl["price"]
        filled += take
        if filled >= qty:
            break
    return cost / filled if filled >= qty else None


print(simulate_fill(snaps[0]["orderbook"]["asks"], 500))

Step 7: Survive contact with production

A bot that trades correctly for an hour and then wedges is worse than no bot. These are the failure modes that actually occur, in rough order of how often they bite:

FailureWhat you seeHandling
Rate limitedLatency climbing, not errorsCloudflare throttles rather than rejecting — watch p99, not error rate
Matching engine restart425 Too EarlyRetry with backoff; do not alert on singles
Exchange paused503Stop opening. Cancel if permitted
Cancel-only mode503 on new ordersCancel path must work independently of the entry path
Post-only mode503 + retry_after_secondsSleep exactly that long, then retry
Signer mismatch400 order signer errorStale credentials from another wallet — re-derive
WebSocket dropSilenceNo data is not the same as no change — track last-message age
Clock drift401 on a bot that worked yesterdayRun NTP; this one wastes the most time
  • Test the cancel path before the entry path. Being unable to exit is far worse than being unable to enter.
  • Cap exposure in code, not in your head. A hard position limit turns a logic bug into a small loss instead of an unbounded one.
  • Build a kill switch and actually test it. An untested kill switch is a comment.
  • Add jitter to scheduled work. Workers waking on the exact second create a synchronised spike far above your average rate.
  • Log every request and response for the first week. You will need them, and you cannot reconstruct them later.
  • Alert on categories, not messages. Polymarket embeds order IDs and prices in error strings, so raw-string alerting is pure noise.

A realistic build order

The sequence that wastes the least time and money:

  1. Read-only integration, public client, no keys. Prove discovery, parsing, pagination and backoff. One day, free.
  2. Backtest the strategy on historical books with honest fill simulation. Most ideas die here. That is the point.
  3. Paper trade live for at least a week — log intended orders instead of sending them. Proves uptime, reconnects and timing.
  4. Go live at minimum size with a hard exposure cap and a tested kill switch.
  5. Scale only when live numbers match paper numbers. If they diverge, your fill model was wrong — return to step 2.

Steps 1, 3 and 4 you can build in a few days. Step 2 needs data Polymarket does not keep, and it is also the step that most reliably saves money. Start free on PolyTest — no card, and enough to find out whether your idea survives the spread.

Frequently asked questions

How do I build a Polymarket trading bot?
Install the current unified SDK — polymarket-client for Python or @polymarket/client for TypeScript. Use AsyncPublicClient to discover markets and read prices without credentials, then AsyncSecureClient with a private key to trade. Subscribe to the WebSocket market channel rather than polling for prices, place orders with place_market_order, and handle 425, 429 and 503 responses with backoff. Before going live, backtest against historical order book snapshots with realistic fill simulation, since Polymarket has no testnet and keeps no order book history.
What SDK should I use for a Polymarket bot in 2026?
Use the unified SDKs: polymarket-client for Python or @polymarket/client for TypeScript. These replace the older py-clob-client, @polymarket/clob-client-v2, @polymarket/builder-relayer-client and @polymarket/builder-signing-sdk packages. A unified Rust SDK is in development. Most tutorials and AI-generated code still reference the deprecated packages, so check the imports before trusting a snippet.
Can I paper trade on Polymarket before risking real money?
Not through Polymarket — there is no testnet, sandbox or demo API. You can build a paper-trading shim yourself by running your live strategy loop against production market data and logging intended orders with the book state at that instant instead of submitting them. That proves your bot is operationally sound, but it assumes fills, so it will not teach you about queue position or market impact.
How do I backtest a Polymarket trading bot?
You need historical order book snapshots from a third party, because Polymarket's API returns 404 for a resolved market's book and an empty array for its price history. Pull point-in-time snapshots with full depth, replay them chronologically without peeking ahead, and simulate fills by walking the book levels rather than assuming mid-price. Up/Down spreads of 2-5 cents on a $1 contract are usually larger than the claimed edge, so mid-price backtests are misleading.
Why does my Polymarket bot get Unauthorized/Invalid api key?
Most often one of two things. Either your credential set is incomplete — L2 authentication needs the API key, secret and passphrase together — or your system clock has drifted, since the request signature embeds a timestamp and a few seconds of drift invalidates it. Less commonly, you derived credentials against a different wallet than the one you are trading from.
Do I need a WebSocket for a Polymarket bot or can I poll?
Use the WebSocket. Polling /book on a timer burns rate limit budget, adds latency and scales poorly. Subscribe to the CLOB market channel for public book and price updates, and the user channel for your own order and trade updates. If you connect to the raw WebSocket rather than through an SDK, send a PING every 10 seconds to keep the connection alive.

Get the historical data Polymarket does not keep

PolyTest records Polymarket crypto Up/Down markets as they run — 90M+ snapshots with 8 levels of order book depth, sub-second timestamps, and resolved markets preserved. Free tier, no card.

Keep reading