Polymarket API & Data Access Guide
Purpose: Comprehensive reference for connecting to, understanding, and using Polymarket APIs
Last Updated: 2026-03-23
🎯 What This Skill Covers
- ✅ Connect to Polymarket - API endpoints, authentication
- ✅ Find Markets - Search, filter, time-based pattern discovery
- ✅ Get Real-time Data - WebSocket usage and event types
- ✅ Access Order Books - Liquidity, bid/ask spreads, depth
- ✅ Place Orders - CLOB SDK, market/limit orders, decimal precision
- ✅ Market Mechanics - How 5-min markets work, winner determination, timing
- ✅ Known Pitfalls - Data reliability, timing issues, common bugs
This skill does NOT include:
- ❌ Trading strategies
- ❌ Signal analysis methodology
🏗️ Architecture Overview
API Components
| Component | URL | Purpose | Auth? |
|-----------|-----|---------|-------|
| Gamma API | https://gamma-api.polymarket.com | Market info, prices, metadata | No |
| CLOB API | https://clob.polymarket.com | Order books, trades, market structure | No |
| CLOB Auth API | https://clob-auth.polymarket.com | JWT token for trading | Yes |
| Data API | https://data-api.polymarket.com | Historical data, order books | No |
| WebSocket | wss://ws-subscriptions-clob.polymarket.com/ws/market | Real-time price/book updates | No |
Data Flow for 5-Minute BTC Up/Down Markets
1. Generate timestamp → slug: btc-updown-5m-{timestamp}
2. Fetch market info from Gamma API → get asset IDs
3. Subscribe to WebSocket with asset IDs → real-time bid/ask
4. Place orders via CLOB SDK → buy UP or DOWN tokens
5. Market closes (5 min) → Polymarket resolves winner
6. Verify winner via Gamma API outcomePrices (1.0 = winner)
🔍 Finding Markets
5-Minute Markets — Timestamp Pattern
import time
def get_current_interval(interval_seconds=300):
"""Calculate current 5-minute interval timestamp"""
current_time = int(time.time())
return (current_time // interval_seconds) * interval_seconds
def generate_market_timestamps(num_markets=8):
"""Generate N market timestamps: current + next (N-1) intervals"""
base = get_current_interval()
return [base + i * 300 for i in range(num_markets)]
# Supported cryptos: btc, eth, sol, xrp
# Slug pattern: {crypto}-updown-5m-{timestamp}
# Example: btc-updown-5m-1772699400
API Search Methods
import requests
# By slug (most reliable for 5-min markets)
resp = requests.get("https://gamma-api.polymarket.com/markets", params={"slug": "btc-updown-5m-1772699400"})
# By keyword search
resp = requests.get("https://gamma-api.polymarket.com/markets", params={"_s": "bitcoin"})
# Active markets
resp = requests.get("https://gamma-api.polymarket.com/markets", params={"active": "true", "limit": 100})
🆔 Asset IDs (Token IDs)
Critical: Each outcome (UP/DOWN) has a unique asset ID required for WS subscriptions and trading.
Getting Asset IDs
import json
def get_asset_ids(market):
"""Get asset IDs from clobTokenIds field (preferred)"""
clob_token_ids_str = market.get('clobTokenIds')
if clob_token_ids_str:
return json.loads(clob_token_ids_str) # List of 2 IDs: [UP_ID, DOWN_ID]
return []
Important:
- ✅
clobTokenIdsis a JSON string, not an array — mustjson.loads() - ✅ Index 0 = UP, Index 1 = DOWN (for btc-updown markets)
- ⚠️ Fall back to
tokens[].token_idonly ifclobTokenIdsmissing
📡 WebSocket — Real-time Data
Connection & Subscription
import aiohttp, asyncio, json, time
async def monitor_market(asset_ids):
async with aiohttp.ClientSession() as session:
ws = await session.ws_connect(
"wss://ws-subscriptions-clob.polymarket.com/ws/market",
heartbeat=None,
timeout=aiohttp.ClientWSTimeout(ws_close=30.0)
)
# Subscribe
await ws.send_json({
"assets_ids": asset_ids,
"type": "market",
"custom_feature_enabled": True
})
# Heartbeat every 10 seconds
last_ping = time.time()
async for msg in ws:
if msg.data == "PONG": continue
if time.time() - last_ping >= 10:
await ws.send_str("PING")
last_ping = time.time()
if msg.type == aiohttp.WSMsgType.TEXT:
data = json.loads(msg.data)
# Handle events...
⚠️ CRITICAL: Event Type Reliability
| Event Type | Description | Reliability | Use? |
|------------|-------------|-------------|------|
| book | Full orderbook snapshot | ⭐⭐⭐ | ✅ YES |
| best_bid_ask | Best bid/ask update | ⭐⭐⭐ | ✅ YES |
| price_change | Past trade records | ⭐ | ❌ NO |
| last_trade_price | Last trade price | ⭐ | ❌ NO |
🔥 Key Lesson: price_change = past trades, NOT current orderbook state. Using it as real-time bid/ask causes impossible states (ask < bid). Only use book and best_bid_ask.
⚠️ CRITICAL: Book Event Structure
# ❌ WRONG: bids/asks are NOT under 'book' key
bids = data.get('book', {}).get('bids', []) # EMPTY!
# ✅ CORRECT: bids/asks at TOP LEVEL
bids = data.get('bids', [])
asks = data.get('asks', [])
Order Book Fields
# Each bid/ask entry:
{
'price': '0.4550', # Price level (0-1)
'size': '7270' # Size in USD at this price
}
# Liquidity = sum of all bid sizes + sum of all ask sizes
total_liquidity = sum(float(b.get('size', 0)) for b in bids) + sum(float(a.get('size', 0)) for a in asks)
💰 Placing Orders — CLOB SDK
Authentication
from py_clob_client.client import ClobClient
from py_clob_client.clob_types import ApiCreds
host = "https://clob.polymarket.com"
chain_id = 137 # Polygon
client = ClobClient(host, key=key, chain_id=chain_id)
client.set_api_creds(ApiCreds(
api_key="YOUR_API_KEY",
api_secret="YOUR_API_SECRET",
api_passphrase="YOUR_API_PASSPHRASE"
))
🔥 CRITICAL: Decimal Precision Rules
| Amount Type | Max Decimals | Example | |-------------|-------------|---------| | Maker amount (tokens) | 4 | 10.7419 | | Taker amount (USD) | 2 | 3.33 | | Price | 4 | 0.3100 |
Rule: Calculate tokens FIRST, then derive USD
# ✅ CORRECT: Explicit precision control
raw_tokens = amount_usd / price # e.g. 3.33 / 0.31 = 10.74193548
tokens = round(raw_tokens, 4) # 10.7419 (max 4 decimals)
usd_amount = round(tokens * price, 2) # 3.33 (max 2 decimals)
price_rounded = round(price, 4) # 0.3100 (max 4 decimals)
# ❌ WRONG: Let SDK auto-calculate → may produce > 4 decimal tokens
# ❌ WRONG: Market order with SDK auto-calculation → same issue
Limit Order (Recommended — More Control)
from py_clob_client.order_builder.constants import BUY
order_args = OrderArgs(
token_id=asset_id,
price=price_rounded, # 0.3100 (max 4 decimals)
size=tokens, # 10.7419 (max 4 decimals)
side=BUY,
expiration=0, # GTC (Good Till Cancelled)
)
# A limit order at market price = instant fill with full precision control
Market Order (From Official Docs)
market_order = client.create_market_order(
token_id="TOKEN_ID",
side=BUY,
amount=100, # Dollar amount! SDK calculates tokens
price=0.50, # Slippage protection (can be 0)
order_type=OrderType.FOK
)
client.post_order(market_order, OrderType.FOK)
Market Order Notes:
amount= USD amount (SDK calculates tokens internally)price= slippage protection ceiling, NOT exact execution price- ⚠️ SDK may produce >4 decimal tokens → prefer limit orders for safety
⚠️ ALWAYS Read Official Docs Before Trading
- https://docs.polymarket.com/trading/orders/create
- https://docs.polymarket.com/trading/orders/overview
Never guess API behavior — read docs first. Multiple failed versions were caused by not reading official documentation.
⏰ 5-Minute Market Mechanics
How Markets Work
- New market every 5 minutes — timestamp-based:
btc-updown-5m-{unix_ts} - Market duration: Exactly 5 minutes (300 seconds)
- Winner determination: Polymarket resolves based on BTC price movement during the 5-minute interval
- UP wins → outcomePrices: UP=1.0, DOWN=0.0
- DOWN wins → outcomePrices: UP=0.0, DOWN=1.0
- Market appears on Polymarket: ~30-60 seconds before interval start
- Orderbook becomes active: Shortly after market appears
Timing — What Happens When
| Time | Event |
|------|-------|
| T-60s | Market appears on Gamma API |
| T-30s | Orderbook starts getting liquidity |
| T+0 | 5-minute interval starts |
| T+0 ~ T+30s | Ask prices around 0.50, active trading |
| T+240s | Most trading done, prices converge |
| T+300s | Market closes |
| T+300s ~ T+360s | Winner being resolved |
| T+360s+ | Gamma API outcomePrices shows 1.0/0.0 (resolved) |
⚠️ CRITICAL: Winner Resolution Timing
# ❌ WRONG: Fetch winner immediately after market close
# Gamma API returns trading prices (0.60/0.40), NOT resolved prices (1.0/0.0)
# Can take 60+ seconds to resolve
# ✅ CORRECT: Wait for outcomePrices to show 1.0/0.0
# After market close, poll Gamma API until resolved
# outcomePrices [1.00, 0.00] = UP winner confirmed
# outcomePrices [0.00, 1.00] = DOWN winner confirmed
📊 Data Reliability — What to Trust
Price Data Hierarchy
| Data Source | Use For | Accuracy | Latency |
|-------------|---------|----------|---------|
| book (WS) | Orderbook, bid/ask | ⭐⭐⭐ | Real-time |
| best_bid_ask (WS) | Quick price check | ⭐⭐⭐ | Real-time |
| outcomePrices (Gamma) | Market probability | ⭐⭐ | 5-30s delayed |
| outcomePrices (Gamma, resolved) | Winner verification | ⭐⭐⭐ | 60s+ after close |
| price_change (WS) | Past trades only | ❌ | N/A |
| best_bid_ask ask price | Trading probability | ⚠️ | NOT true probability |
⚠️ best_bid_ask Ask Price ≠ Market Probability
- Ask price = lowest sell order in orderbook
- This is not the market's consensus probability
- It reflects orderbook dynamics, not market sentiment
- All versions using ask price as probability showed ~50% accuracy
🔄 Market Rotation
Interval-Based Rotation (Correct Method)
def get_current_interval():
"""Get current 5-minute interval boundary"""
current_time = int(time.time())
return (current_time // 300) * 300
def should_rotate(current_interval, last_interval):
"""Check if we've entered a new interval"""
return current_interval != last_interval
# ❌ WRONG: Time-based (rotates at wrong time)
# time_since_rotation = time.time() - last_rotation
# if time_since_rotation >= 300: rotate() # Off by up to 15 seconds!
# ✅ CORRECT: Interval-based (rotates exactly when interval changes)
# if get_current_interval() != current_base_timestamp: rotate()
⚠️ Known Pitfalls & Lessons Learned
1. Never Use price_change as Orderbook Data
price_change= past trade records- BUY in
price_change= someone bought at this price IN THE PAST - This is NOT the current best ask
- Using it causes impossible states (ask < bid)
2. Decimal Precision Will Break Your Orders
- Maker amount (tokens): max 4 decimals
- Taker amount (USD): max 2 decimals
- Calculate tokens first, derive USD second
- Never trust SDK auto-calculation
- Use limit orders for full control
3. Read Official Docs Before Implementing
- Multiple wasted versions from guessing API behavior
- Official docs: https://docs.polymarket.com/trading/orders/create
4. Winner Resolution Takes Time
- Gamma API doesn't return 1.0/0.0 immediately after close
- Poll until outcomePrices resolves to 1.0/0.0
- Verify with Gamma API outcomePrices post-session
⚠️ Pitfall #5: Market Resolution Timing
- Gamma API doesn't return 1.0/0.0 immediately after close
- Can take 60+ seconds for outcomePrices to resolve
- Poll until you see 1.0/0.0, don't assume immediate resolution
6. Market Appearance Timing is Variable
- Markets don't always appear exactly 60s before start
- Sometimes 30s, sometimes 90s
- Fetch ask immediately when market appears — don't delay
7. Ask Price at Market Start ≈ 0.50
- Fresh market ask is ~0.50 (fair odds)
- Ask drifts quickly (within seconds) based on BTC movement
- If you see ask = 0.99, market is already closed/ending
📚 API Reference
Gamma API Endpoints
| Endpoint | Method | Description | Auth? |
|----------|--------|-------------|-------|
| /markets | GET | List/search markets | No |
| /markets/{id} | GET | Get market by ID | No |
| /markets?slug={slug} | GET | Get market by slug | No |
| /markets/{id}/price | GET | Current prices | No |
WebSocket Events
| Event | Direction | Reliable? | Structure |
|-------|-----------|-----------|-----------|
| book | S→C | ✅ | bids[], asks[] at top level |
| best_bid_ask | S→C | ✅ | best_bid, best_ask |
| price_change | S→C | ❌ | Past trades, not orderbook |
| last_trade_price | S→C | ❌ | Last trade, not orderbook |
Rate Limits
| API | Limit | Best Practice | |-----|-------|---------------| | Gamma API | ~100 req/min | Use WebSocket instead of polling | | Data API | ~50 req/min | Cache responses | | WebSocket | No hard limit | PING every 10s, batch subscriptions |
🔧 CLOB SDK Quick Reference
from py_clob_client.client import ClobClient
from py_clob_client.clob_types import OrderArgs, OrderType, ApiCreds
from py_clob_client.order_builder.constants import BUY
# Initialize
client = ClobClient("https://clob.polymarket.com", key=key, chain_id=137)
client.set_api_creds(ApiCreds(api_key=..., api_secret=..., api_passphrase=...))
# Limit order (recommended)
order = OrderArgs(token_id=ASSET_ID, price=0.50, size=10.0, side=BUY, expiration=0)
# size = tokens (max 4 decimals), price (max 4 decimals)
# Market order
market_order = client.create_market_order(token_id=ASSET_ID, side=BUY, amount=100, price=0, order_type=OrderType.FOK)
# amount = USD, price = slippage ceiling
# Nonces: Must be unique per order. Use counter or UUID.
Official Documentation
- Orders: https://docs.polymarket.com/trading/orders/create
- Overview: https://docs.polymarket.com/trading/orders/overview
📝 Version History
-
v2.0.0 (2026-03-23):
- ✅ Added CLOB SDK trading section (limit/market orders, decimal precision)
- ✅ Added 5-min market mechanics (timing, winner determination)
- ✅ Added data reliability guide (what to trust, what not to)
- ✅ Added known pitfalls section (7 critical lessons)
- ✅ Added winner verification timing guide
- ✅ Added winner verification timing guide
- ✅ Added market rotation (interval-based vs time-based)
- ✅ Updated WebSocket reliability table
- ✅ Removed arbitrage scanner example (not applicable to 5-min markets)
-
v1.2.0 (2026-03-05):
- ✅ Fixed WebSocket book event structure
- ✅ Added real-time liquidity tracking
- ✅ Added multi-cryptocurrency monitoring
-
v1.0.0 (2026-03-03):
- Initial release
微信扫一扫