Skill flagged — suspicious patterns detected

ClawHub Security flagged this skill as suspicious. Review the scan results before using.

Polymarket API Guide

v2.0.0

Polymarket 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...

0· 394· 4 versions· 1 current· 1 all-time· Updated 13h ago· MIT-0

Install

openclaw skills install innotech-polymarket-api

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

  1. Connect to Polymarket - API endpoints, authentication
  2. Find Markets - Search, filter, time-based pattern discovery
  3. Get Real-time Data - WebSocket usage and event types
  4. Access Order Books - Liquidity, bid/ask spreads, depth
  5. Place Orders - CLOB SDK, market/limit orders, decimal precision
  6. Market Mechanics - How 5-min markets work, winner determination, timing
  7. Known Pitfalls - Data reliability, timing issues, common bugs

This skill does NOT include:

  • ❌ Trading strategies
  • ❌ Signal analysis methodology

🏗️ Architecture Overview

API Components

ComponentURLPurposeAuth?
Gamma APIhttps://gamma-api.polymarket.comMarket info, prices, metadataNo
CLOB APIhttps://clob.polymarket.comOrder books, trades, market structureNo
CLOB Auth APIhttps://clob-auth.polymarket.comJWT token for tradingYes
Data APIhttps://data-api.polymarket.comHistorical data, order booksNo
WebSocketwss://ws-subscriptions-clob.polymarket.com/ws/marketReal-time price/book updatesNo

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:

  • clobTokenIds is a JSON string, not an array — must json.loads()
  • ✅ Index 0 = UP, Index 1 = DOWN (for btc-updown markets)
  • ⚠️ Fall back to tokens[].token_id only if clobTokenIds missing

📡 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 TypeDescriptionReliabilityUse?
bookFull orderbook snapshot⭐⭐⭐✅ YES
best_bid_askBest bid/ask update⭐⭐⭐✅ YES
price_changePast trade records❌ NO
last_trade_priceLast 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 TypeMax DecimalsExample
Maker amount (tokens)410.7419
Taker amount (USD)23.33
Price40.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

Never guess API behavior — read docs first. Multiple failed versions were caused by not reading official documentation.


⏰ 5-Minute Market Mechanics

How Markets Work

  1. New market every 5 minutes — timestamp-based: btc-updown-5m-{unix_ts}
  2. Market duration: Exactly 5 minutes (300 seconds)
  3. 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
  4. Market appears on Polymarket: ~30-60 seconds before interval start
  5. Orderbook becomes active: Shortly after market appears

Timing — What Happens When

TimeEvent
T-60sMarket appears on Gamma API
T-30sOrderbook starts getting liquidity
T+05-minute interval starts
T+0 ~ T+30sAsk prices around 0.50, active trading
T+240sMost trading done, prices converge
T+300sMarket closes
T+300s ~ T+360sWinner 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 SourceUse ForAccuracyLatency
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 onlyN/A
best_bid_ask ask priceTrading 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

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

EndpointMethodDescriptionAuth?
/marketsGETList/search marketsNo
/markets/{id}GETGet market by IDNo
/markets?slug={slug}GETGet market by slugNo
/markets/{id}/priceGETCurrent pricesNo

WebSocket Events

EventDirectionReliable?Structure
bookS→Cbids[], asks[] at top level
best_bid_askS→Cbest_bid, best_ask
price_changeS→CPast trades, not orderbook
last_trade_priceS→CLast trade, not orderbook

Rate Limits

APILimitBest Practice
Gamma API~100 req/minUse WebSocket instead of polling
Data API~50 req/minCache responses
WebSocketNo hard limitPING 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


📝 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

Version tags

latestvk975x08fvdgy6v1whd6rp7zqwn83f0nn