Install
openclaw skills install innotech-polymarket-apiPolymarket API and data access guide. Learn how to connect, find markets, get real-time data via WebSocket, access order books, place orders via CLOB SDK, an...
openclaw skills install innotech-polymarket-apiPurpose: Comprehensive reference for connecting to, understanding, and using Polymarket APIs
Last Updated: 2026-03-23
This skill does NOT include:
| 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 |
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)
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
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})
Critical: Each outcome (UP/DOWN) has a unique asset ID required for WS subscriptions and trading.
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:
clobTokenIds is a JSON string, not an array — must json.loads()tokens[].token_id only if clobTokenIds missingimport 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...
| 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.
# ❌ 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', [])
# 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)
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"
))
| 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
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 = 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 priceNever guess API behavior — read docs first. Multiple failed versions were caused by not reading official documentation.
btc-updown-5m-{unix_ts}| 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) |
# ❌ 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 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 Probabilitydef 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()
price_change as Orderbook Dataprice_change = past trade recordsprice_change = someone bought at this price IN THE PAST| 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 |
| 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 |
| 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 |
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.
v2.0.0 (2026-03-23):
v1.2.0 (2026-03-05):
v1.0.0 (2026-03-03):