# Polymarket APIs — Reference

Polymarket exposes four distinct APIs. You don't pick one — a real bot uses all of them. This file is the full map.

## Quick map

| API | Hostname | Auth | What it's for |
|---|---|---|---|
| **Gamma** (Markets) | `https://gamma-api.polymarket.com` | none for reads | Discovering markets/events, metadata, slugs, condition_ids, token_ids |
| **CLOB** (Trading) | `https://clob.polymarket.com` | L1 (wallet) + L2 (HMAC) | Order book, prices, place/cancel orders |
| **Data** | `https://data-api.polymarket.com` | none | Leaderboards, user positions, user activity, trade history |
| **WebSocket — Market** | `wss://ws-subscriptions-clob.polymarket.com/ws/market` | none | Real-time book + trade data per token |
| **WebSocket — User** | `wss://ws-subscriptions-clob.polymarket.com/ws/user` | L2 HMAC | Personal order/fill events |
| **WebSocket — Sports** | `wss://sports-api.polymarket.com/ws` | none | Live sports scores |
| **WebSocket — RTDS** | `wss://ws-live-data.polymarket.com` | none | Crypto prices (Binance, Chainlink) used by markets |
| **The Graph subgraphs** | hosted | API key | Historical aggregations, on-chain trade indexing |

## Hierarchy of objects

```
Series (occasionally)
  └── Event           — slug like "how-many-fed-rate-cuts-in-2025", id like 16085
        ├── Market    — one per outcome option, identified by condition_id (0x…)
        │     ├── Outcome 0 (e.g. Yes / option A) → token_id (huge integer)
        │     └── Outcome 1 (e.g. No / option B)  → token_id (huge integer)
        └── Market    — second outcome bucket, same shape
        └── …
```

A **token_id** is what you actually trade. It's a uint256 on Polygon. Always work with it as a string in your code — Python ints work, but JSON serialization will silently truncate or alter it on round-trips. Treat token_ids as opaque strings.

## Gamma API (market discovery)

REST, no auth, ~10 req/s public limit.

Most-used endpoints:

```
GET /markets
  query params:
    limit, offset
    closed=false      # active only
    active=true
    archived=false
    liquidity_num_min=5000
    volume_num_min=10000
    tag_id=politics
    order=volume24hr&ascending=false

GET /markets/{condition_id}
GET /markets?slug=will-fed-cut-rates-in-march

GET /events
  same shape, returns the event with its nested markets[]

GET /search?query=election
  full-text across events/tags/profiles
```

The `/markets` response includes `clob_token_ids` — a JSON-stringified array of the two (or N) token_ids for that market. Parse it before using.

Code:
```python
import httpx

GAMMA = "https://gamma-api.polymarket.com"

async with httpx.AsyncClient(timeout=10.0) as c:
    r = await c.get(f"{GAMMA}/markets",
                    params={"limit": 50, "closed": "false",
                            "liquidity_num_min": 5000})
    markets = r.json()
    for m in markets:
        token_ids = json.loads(m["clob_token_ids"])
        print(m["question"], "->", token_ids)
```

Cache aggressively. Market metadata changes slowly; refresh active-list every 60s, full re-scan every hour.

## CLOB API (trading)

REST + WebSocket. Two auth levels.

### Authentication

**L1 — wallet signature (EIP-712).** Required only to create or recover API credentials. The SDK does this for you.

**L2 — HMAC API credentials.** Required for placing/cancelling orders, viewing your own orders/trades/balances. You pass `api_key + api_secret + api_passphrase` and the SDK signs each request.

```python
from py_clob_client.client import ClobClient

# v1 (production-stable, ~1.1M monthly downloads)
client = ClobClient(
    host="https://clob.polymarket.com",
    chain_id=137,                       # Polygon mainnet
    key=PRIVATE_KEY,
    signature_type=2,                   # see authentication.md
    funder=PROXY_FUNDER_ADDRESS,
)
client.set_api_creds(client.create_or_derive_api_creds())
```

### v1 vs v2

`py_clob_client` v0.34.6 is the production version. `py_clob_client_v2` and `clob-client-v2` (TS) were published in April 2026 with minimal initial activity. **Default to v1** unless the user explicitly wants v2 — its API is similar but not identical and migration notes are still being written.

### Read endpoints (level 0, no auth)

```python
client = ClobClient("https://clob.polymarket.com")  # no key

client.get_ok()                          # health
client.get_server_time()                 # for nonces/timestamps
client.get_simplified_markets()          # cheap market list
client.get_midpoint(token_id)
client.get_price(token_id, side="BUY")
client.get_order_book(token_id)          # OrderBookSummary
client.get_order_books([BookParams(token_id=tid) for tid in ids])  # batch
client.get_last_trade_price(token_id)
client.get_trades()                      # global recent
```

### Write endpoints (need L2 creds)

```python
from py_clob_client.clob_types import OrderArgs, MarketOrderArgs, OrderType
from py_clob_client.order_builder.constants import BUY, SELL

# Limit order (resting GTC)
order = OrderArgs(token_id=tid, price=0.55, size=100.0, side=BUY)
signed = client.create_order(order)
resp = client.post_order(signed, OrderType.GTC)

# Market order (immediate FOK / FAK)
mo = MarketOrderArgs(token_id=tid, amount=25.0, side=BUY,
                     order_type=OrderType.FOK)
signed = client.create_market_order(mo)
resp = client.post_order(signed, OrderType.FOK)
```

`create_and_post_order` exists as a one-shot convenience — preferred in starter scripts.

### Order types

- **GTC** (Good Till Cancelled) — limit order, sits on the book until filled or cancelled. Default for market making, end-game sweep, AI bot.
- **FOK** (Fill or Kill) — fills the entire order at the limit price or better, immediately, or it's cancelled entirely. Default for arbitrage and copy trading.
- **FAK** (Fill and Kill) — fills as much as possible immediately, cancels the rest. Use when partial fills are acceptable.
- **GTD** (Good Till Date) — limit with explicit expiry timestamp.

### Precision rules

Polymarket has finicky precision constraints by tick size and order type:

- Tick size lookup is in `ROUNDING_CONFIG` in `py-clob-client`. Tick sizes change *dynamically* when prices reach edges (>0.96 or <0.04). Always query `/markets/{condition_id}` to confirm tick before submitting.
- **Market FOK orders, sell side:** maker amount limited to **2 decimal places**, taker amount to 4, product `size × price` ≤ 2 decimals. (Documented from API error responses, see py-clob-client issue #121.)
- Regular GTC orders are more flexible.

### Cancel + manage

```python
client.cancel(order_id)
client.cancel_all()
client.cancel_market_orders(market=condition_id)

client.get_orders()                            # your open orders
client.get_orders(market=condition_id)
client.get_trades()                            # your fills

client.get_balance_allowance(BalanceAllowanceParams(
    asset_type=AssetType.COLLATERAL))           # USDC.e
client.get_balance_allowance(BalanceAllowanceParams(
    asset_type=AssetType.CONDITIONAL,
    token_id=tid))                              # specific outcome token
```

### Trade lifecycle (status)

After an order matches, the trade goes through these statuses (delivered via WebSocket):

| Status | Meaning |
|---|---|
| MATCHED | Off-chain match, sent to executor |
| MINED | Tx in a Polygon block, no finality yet |
| CONFIRMED | Strong probabilistic finality, success |
| RETRYING | Tx reverted/reorged, operator retrying |
| FAILED | Final failure, no retry |

**Don't mark a trade as done on MATCHED.** Wait for CONFIRMED in your bookkeeping; surface RETRYING in the dashboard so the user knows.

## Data API (analytics)

REST, no auth, ~60 req/s.

Most-used endpoints:

```
GET /leaderboard?window=1d|7d|30d|all&sort=pnl|volume
  → [{ rank, proxyWallet, userName, vol, pnl, profileImage,
       xUsername, verifiedBadge }]

GET /activity?user=<proxy_wallet>
  optional: market=<condition_id>, type=TRADE|SPLIT|MERGE|REDEEM,
            start=<unix>, end=<unix>, side=BUY|SELL,
            sortBy=TIMESTAMP|TOKENS|CASH, sortDirection=ASC|DESC
  → list of trade/activity rows including transactionHash

GET /positions?user=<proxy_wallet>
  → list of {asset, conditionId, size, avgPrice, initialValue,
              currentValue, cashPnl, percentPnl, totalBought,
              realizedPnl, percentRealizedPnl, curPrice,
              redeemable, title, slug, outcome, …}
```

For copy trading and whale tracking, the standard pipeline is:

1. Hit `/leaderboard` (or your watchlist) to get the wallet set.
2. For each, poll `/activity` every 1–2s.
3. Dedup new rows by `transactionHash`.
4. Filter and act.

## WebSocket — Market channel

Public, real-time orderbook + trades.

```
wss://ws-subscriptions-clob.polymarket.com/ws/market
```

Subscribe message:
```json
{
  "assets_ids": ["<token_id_1>", "<token_id_2>"],
  "type": "market",
  "custom_feature_enabled": true
}
```

Setting `custom_feature_enabled: true` adds `best_bid_ask`, `new_market`, and `market_resolved` events on top of the base set.

Event types and shapes:

- **book** — full orderbook snapshot, fired on subscribe and after any trade affecting it
- **price_change** — incremental update; size `"0"` means the level was removed
- **tick_size_change** — fires when price crosses 0.96 or 0.04 thresholds
- **last_trade_price** — every trade
- **best_bid_ask** — `{ best_bid, best_ask, spread, timestamp }` (custom)
- **new_market** — emits a full market object including `condition_id`, `clob_token_ids`, `tags`
- **market_resolved** — final resolution

Limits:

- Polymarket enforces ~500 instruments per WS connection (undocumented). For more, fan out across multiple connections.
- Rate limits are Cloudflare-throttled; HTTP 429 indicates throttling, not permanent block.
- Subscriptions on existing WS via `{"operation":"subscribe","markets":[…]}` work for some endpoints but **unsubscribing is not supported** — to drop subs, close and reopen.

Reconstruction algorithm:

1. On subscribe, the server sends a `book` snapshot — store it as the baseline.
2. Apply incoming `price_change` events to the local book.
3. On `tick_size_change`, the rounding hierarchy changes — update your tick.
4. **On disconnect** — refetch `/book?token_id=…` REST snapshot before resubscribing. You will have missed events.

```python
import asyncio, json, websockets

async def market_ws(token_ids: list[str], on_event):
    url = "wss://ws-subscriptions-clob.polymarket.com/ws/market"
    while True:
        try:
            async with websockets.connect(url, ping_interval=10) as ws:
                await ws.send(json.dumps({
                    "assets_ids": token_ids,
                    "type": "market",
                    "custom_feature_enabled": True,
                }))
                async for raw in ws:
                    for event in json.loads(raw):
                        await on_event(event)
        except Exception as e:
            await asyncio.sleep(2)  # reconnect with backoff
```

A solid TypeScript implementation that handles all the edge cases: `@nevuamarkets/poly-websockets` (auto-reconnect, dynamic subscribe, derived price events). Useful as a reference even if you write Python.

## WebSocket — User channel

Personal order lifecycle. Same host, `/ws/user`.

Subscribe with auth + `markets` list of condition_ids you care about.

Events: `trade` and `order` — fired on every state change of your own orders.

You don't really need this for read-only bots, but for execution-aware bots it's how you avoid polling `/orders`.

## WebSocket — Sports channel

```
wss://sports-api.polymarket.com/ws
```

No subscription needed — connect and you receive `sport_result` for every active game. Heartbeat: server pings every 5s, client must pong within 10s. Useful for sports prediction markets where Polymarket has live games.

## WebSocket — RTDS (Real-Time Data Socket)

```
wss://ws-live-data.polymarket.com
```

Streams the crypto reference prices used by the 5-min markets, plus comments. Subscribe to `crypto_prices_chainlink` with a filter for `btc/usd | eth/usd | sol/usd | xrp/usd`. **This is the same oracle Polymarket uses to resolve** — it gives you the strike price exactly, with no lag, no decoding of Chainlink contract round IDs.

## The Graph subgraphs

Used for historical / aggregate queries that the REST APIs aren't optimized for. Hosted by Goldsky. Requires a Graph API key.

Graph Polymarket MCP server (community) exposes 20 tools to AI agents:
`list_subgraphs`, `get_subgraph_schema`, `query_subgraph`, `get_market_data`, `get_global_stats`, `get_account_pnl`, `get_top_traders`, `get_daily_stats`, `get_market_positions`, `get_user_positions`, `get_recent_activity`. Useful for an AI-driven bot that needs ad-hoc analytics.

## Bitquery (alternative on-chain data)

For very-large-scale historical work, Bitquery's GraphQL endpoint indexes Polymarket via Polygon trade data. Use `dataset: realtime` for last ~7 days, paid plan for deeper history. Filter with `Marketplace.ProtocolName: "polymarket"` on `network: matic`.

## Rate limits cheat sheet

| Endpoint | Limit |
|---|---|
| Gamma public | ~10 req/s, ~60/min unauth |
| CLOB read (no auth) | ~100 req/min |
| CLOB authenticated | ~100+ req/min, 60 orders/min hard cap |
| Data API | ~60 req/s public |
| WebSocket | 5 concurrent connections per IP, 500 subs per WS |

Implement exponential backoff with `tenacity` for any REST loop. WebSocket usage doesn't count against REST limits — prefer WS wherever you can.

## Required Python deps

```
py-clob-client==0.34.6
python-dotenv>=1.0.0
httpx>=0.27.0
websockets>=12.0
ethers (or web3==7.12.1 for allowance scripts)
tenacity>=8.0.0
rich>=13.7.0          # terminal dashboard
pydantic>=2.0         # typed config / API responses
```

For TS shops: `@polymarket/clob-client` (v1) or `@polymarket/clob-client-v2`, `@nevuamarkets/poly-websockets` for the WS layer.
