Polymarket API with Python: from pip install to your first order

The 2026 way, with the unified polymarket-client SDK: create a secure client from a private key and wallet address, read a market, place a limit order and a market order, wait for settlement, list positions — and the gotchas (tick size, GTD expiry, geoblock, rate limits, the archived py-clob-client) that stop first scripts.

By the POLBOTS editorPublished Aug 21, 20264 min read
On this page9 sections

Before you write code

You need three things: a Polymarket account with some pUSD in it (the quickstart asks for at least ten), the account's wallet address (profile menu on polymarket.com), and the private key of the signer that controls it. Put the last two in environment variables. You also need to be somewhere the venue accepts new orders — the rules guide has the list, and the SDK will not fix geography for you.

One warning before the install: the library most tutorials still show, py-clob-client, is archived and non-functional since the CLOB V2 cutover in April 2026, and so is its -v2 successor. Use the unified SDK.

Install

pip install polymarket-client

The package exposes two pairs of clients: PublicClient / AsyncPublicClient for reading markets, books and prices without credentials, and SecureClient / AsyncSecureClient for anything that signs — placing and cancelling orders, reading your own positions. Use the async ones; a bot spends its life waiting on the network.

Create a client

import os
import asyncio
from polymarket import AsyncSecureClient

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

asyncio.run(main())

create does the authentication dance for you: it signs the one-time L1 attestation with your key, derives (or creates) the L2 API credentials the CLOB expects on every trading request, and works out which kind of wallet your account is — deposit wallet, proxy, Safe or plain EOA — so orders are signed correctly. With older libraries each of those was a separate, error-prone step.

Find a market and its tokens

Every outcome you can trade is a token with its own id. Fetch the market — the SDK accepts the market's page URL — and take the token id of the outcome you want:

market = await client.get_market(url="https://polymarket.com/event/<event-slug>")
# each outcome on the market carries a token_id; pick the side you want to trade
token_id = market.tokens[0].token_id   # field names per the current SDK docs

The market object is also where the two facts that shape your order live: the tick size (0.01, 0.001 or 0.0001) and whether it is a negative-risk market. The order methods resolve both for you, but you want to know them — a 0.01 market cannot take a price of 0.505, and a neg-risk market is priced as one outcome among several that must sum to a dollar.

Place a limit order

resp = await client.place_limit_order(
    token_id=token_id,
    side="BUY",
    price=0.42,
    size=25,          # shares
    post_only=True,   # add liquidity only; reject if it would match immediately
)

A resting order pays no fee — only takers do — and post_only=True guarantees you never become a taker by accident. Leave expiration unset for good-till-cancelled, or pass a Unix timestamp for good-till-date, remembering the CLOB's quirk: a GTD order expires one minute before the time you give, and the time must be at least a few minutes out, so an order you want alive for N seconds is set to now + 60 + N.

Place a market order and wait for it to settle

est = await client.estimate_market_price(token_id=token_id, side="BUY", amount=10)

resp = await client.place_market_order(
    token_id=token_id,
    side="BUY",
    amount=10,           # pUSD to spend (or pass shares=...)
    max_price=0.45,      # worst price you will accept
)
tx_hashes = await client.wait_for_order_fill_settlement(resp)

A market order is fill-and-kill by default: it takes what the book offers up to your limit and cancels the rest. You pay the taker fee on what fills — on a crypto market at a 50-cent price that is 1.75% of notional, on a politics market 1%, on geopolitics nothing — so estimate_market_price first is not optional for anything you intend to run in a loop. Settlement is asynchronous: the fill is reported at once and lands on Polygon a few seconds later, which is what wait_for_order_fill_settlement (default timeout about thirty seconds) is for. On markets with a matching delay the response comes back with status "delayed" and matches when the delay elapses.

See what you hold

positions = await client.list_positions()
mine = [p for p in positions if p.condition_id == market.condition_id]

For anything beyond the SDK's helpers, the REST endpoints are stable and documented: GET /book?token_id=… for the full book (with tick size and neg-risk), /price, /midpoint, /spread, /prices-history; the Data API for any wallet's positions and trades; and the WebSocket market channel for live updates — which is what you should use instead of polling /book in a loop.

The gotchas, collected

  • Rate limits. Book, price and midpoint reads are capped at 1,500 per ten seconds per IP; single-order placement at 5,000 in a burst; and since July 2026 placing and cancelling also draw from per-signer token buckets. A 429 now carries Retry-After — honour it.
  • Tick size and minimum size. Round to the market's tick and respect its minimum order size, or the order is rejected with a message that says exactly that.
  • GTD expiry. One minute early, and at least a few minutes out.
  • Geoblock. Call https://polymarket.com/api/geoblock at start-up; if blocked is true, stop, instead of learning it from rejected orders.
  • Secrets. Environment variables or a secrets manager; never in the repo, never in a chat.
  • No paper mode in the SDK. Build a dry-run flag on day one and charge yourself spread and fee in it; the backtesting guide explains why that matters.

Where to go from here

Polymarket's own Agents framework is a complete, MIT-licensed Python starting point that wires the APIs above to an LLM; the open-source collection has working Python bots — a minute-market bot and the Poly-Maker market maker — whose code is worth reading before you write your own. For the whole loop from strategy to deployment, continue with how to build a Polymarket bot.

Frequently asked questions

Should I use py-clob-client?
No. It is archived and its README says it no longer works — it was built for CLOB V1, which was switched off in April 2026. The interim py-clob-client-v2 is deprecated as well. The current official package is polymarket-client, which ships both a public (read) client and a secure (trading) client, async and sync.
Where do the wallet address and private key come from?
The wallet address is your Polymarket account's address, shown in the profile menu on polymarket.com; the private key belongs to the signer that controls that account. For a bot, the sane setup is a dedicated account funded only with what the bot trades, with the key loaded from an environment variable — never from a file in the repository and never pasted into anything.
Is there a paper-trading mode in the SDK?
Not in the official SDK. Several bots in the catalog ship their own dry-run mode, and the honest ones calibrate it against real fills; if you build your own, the minimum is a flag that logs the order it would have sent instead of sending it, and a fill model that charges you the spread and the taker fee.
Why was my order rejected with a tick-size error?
Because the price did not sit on the market's tick. Each market allows 0.01, 0.001 or 0.0001 increments, read from the order book; 0.505 is fine in a 0.001 market and invalid in a 0.01 one. The unified SDK resolves tick size for you on the order methods, so this mostly bites people building raw requests or rounding prices themselves.