PolyBot communicates with Polymarket exclusively through the official CLOB (Central Limit Order Book) API. This page documents exactly how the integration works — from authentication to order placement to WebSocket streaming — so you can understand what the bot is doing under the hood and troubleshoot connectivity issues.
Every time PolyBot places a trade, the following sequence occurs:
POST /order on the CLOB.fill_timeout_seconds, it is cancelled and retried at the new market price (up to fill_retry_max times).DELETE /orders before the process exits.In Paper Mode and Dry-Run Mode, steps 3–7 are fully bypassed — the bot logs what it would have done without touching any real order endpoints.
Polymarket uses a two-layer authentication system. Your L1 wallet (MetaMask / WalletConnect) proves ownership of your funds. Your L2 API key is a separate ephemeral keypair that authorises individual API requests without exposing your wallet's private key.
polymarket.com/profile/api-keys.| Field | Description | Example Format |
|---|---|---|
| api_key | Public identifier for your L2 key. Used in the POLY-API-KEY header. |
a1b2c3d4-e5f6-... (UUID format) |
| api_secret | Private key used to sign request payloads. Never share this. | ABC123xyz... (base64, 44 chars) |
| passphrase | Additional authentication factor required on every signed request. | Alphanumeric string you set at creation |
api_secret and passphrase should never be committed to Git, shared in Discord, or sent to any third party including PolyBot support. PolyBot never asks for your credentials — all keys are stored locally in your encrypted config.yml.
The Polymarket CLOB is a centralised matching engine that settles trades on-chain on Polygon. PolyBot interacts with two surfaces:
# REST API https://clob.polymarket.com # WebSocket wss://ws-subscriptions-clob.polymarket.com/ws/
| Method | Endpoint | PolyBot Uses It For |
|---|---|---|
| GET | /markets | Fetching all active markets for strategy scanning |
| GET | /book?token_id={id} | Reading live order book depth for liquidity guards |
| POST | /order | Placing GTC limit orders (signed L2 request) |
| GET | /orders?maker_address={addr} | Fetching open orders for fill status checks |
| DELETE | /order/{orderId} | Cancelling individual unfilled orders |
| DELETE | /orders | Cancel-all on graceful bot shutdown |
| GET | /trades?maker_address={addr} | Pulling fill history for P&L tracking and retraining |
All orders placed by PolyBot are GTC (Good-Till-Cancelled) limit orders. Market orders are not used — they would expose the bot to unbounded slippage, especially in thin books. Instead, PolyBot places the order at the current best ask (for buys) or best bid (for sells) and waits for a fill.
# Internal order construction (simplified)
{
"order": {
"salt": 1718000000123,
"maker": "0xYourWalletAddress",
"signer": "0xYourL2SignerAddress",
"taker": "0x0000000000000000000000000000000000000000",
"tokenId": "91234567890...", # YES token ID for this market
"makerAmount": "50000000", # 50 USDC (6 decimal USDC)
"takerAmount": "97087378", # Amount of YES tokens at ~0.515
"expiration": "0", # GTC — no expiry
"nonce": "0",
"feeRateBps": "100", # 1% fee in basis points
"side": "BUY",
"signatureType": 0
},
"signature": "0x...",
"owner": "0xYourL2SignerAddress"
}
If an order is not filled within fill_timeout_seconds (default: 45s), PolyBot cancels it and re-evaluates whether the signal is still valid. If the strategy still fires, a new order is placed at the updated book price. This continues up to fill_retry_max (default: 3) attempts before the signal is abandoned.
global: fill_timeout_seconds: 45 # Cancel unfilled orders after 45s fill_retry_max: 3 # Max re-attempts per signal fill_retry_delay_seconds: 5 # Wait 5s between retry attempts
PolyBot maintains two persistent WebSocket connections during operation:
Subscribes to real-time price updates for all markets being actively monitored. PolyBot uses this to update the feature vector for the ML model between REST API polls and to detect mid-window price spikes that may trigger Multi-Entry.
# Price feed subscription message (sent on connect)
{
"auth": {
"apiKey": "your-api-key",
"secret": "your-api-secret",
"passphrase": "your-passphrase"
},
"type": "Market",
"markets": ["0xMarketConditionId1", "0xMarketConditionId2"]
}
Subscribes to fill events for PolyBot's own open orders. When a fill event arrives, PolyBot immediately updates its internal position tracker, records the fill price and size, and notifies the risk management layer.
# User order subscription (authenticated)
{
"auth": {
"apiKey": "your-api-key",
"secret": "your-api-secret",
"passphrase": "your-passphrase"
},
"type": "User",
"markets": [] # Empty = subscribe to all your orders
}
If either WebSocket connection drops, PolyBot attempts reconnection with exponential backoff (1s, 2s, 4s, 8s… up to 60s). If reconnection fails after 5 attempts, a Telegram alert is sent and the bot enters a safe hold state — no new trades until the connection is restored.
Polymarket's CLOB API enforces the following rate limits. PolyBot is designed to stay comfortably within them under normal operation.
| Limit Type | Value | How PolyBot Handles It |
|---|---|---|
| REST requests | 10 req/s sustained | Internal token bucket limiter; queues excess requests |
| Order placement burst | 20 orders/s | Multi-Entry orders are batched with 50ms delay between each |
| WebSocket messages | No published limit | Subscriptions are batched; single WS per surface |
| Cancel requests | 5 req/s | Shutdown cancel-all is throttled to 4 req/s |
Snipe Mode's edge comes from reading the Chainlink BTC/USD price feed directly from the Polygon blockchain — the same feed Polymarket uses to resolve BTC price markets. This is an on-chain read, not an API call, so it is not subject to CLOB rate limits.
# Chainlink BTC/USD Feed on Polygon Feed Address: 0xc907E116054Ad103354f2D350FD2514433D57F6f Interface: AggregatorV3Interface Function: latestRoundData() Returns: (roundId, answer, startedAt, updatedAt, answeredInRound) # PolyBot reads the feed via eth_call (no gas required) # answer is the price in 8 decimal places # e.g. answer = 6750000000000 → BTC = $67,500.00
PolyBot polls the Chainlink feed once every 5 seconds during the final 60 seconds of each 15-minute window. If the feed price diverges from the current CLOB mid-price by more than the configured min_edge_pct, Snipe Mode fires a trade on the correctly-priced side.
fifteenm_bot:
chainlink:
rpc_url: "https://polygon-rpc.com" # Or your own Polygon RPC
feed_address: "0xc907E116054Ad103354f2D350FD2514433D57F6f"
poll_interval_seconds: 5
confirmation_blocks: 1 # Read after 1 block confirmation
The recommended way to supply API credentials to PolyBot is via Docker Compose environment variables. This keeps secrets out of your config.yml and makes it easy to rotate keys without editing multiple files.
# docker-compose.yml
version: "3.9"
services:
polybot-copy:
image: polybot/copy-bot:latest
container_name: polybot_copy
restart: unless-stopped
environment:
- POLY_API_KEY=${POLY_API_KEY}
- POLY_API_SECRET=${POLY_API_SECRET}
- POLY_PASSPHRASE=${POLY_PASSPHRASE}
- POLY_CHAIN_ID=137 # 137 = Polygon mainnet
- POLY_WALLET_ADDRESS=${WALLET_ADDRESS}
- TELEGRAM_BOT_TOKEN=${TG_BOT_TOKEN}
- TELEGRAM_CHAT_ID=${TG_CHAT_ID}
volumes:
- ./config.yml:/app/config.yml:ro
- ./data:/app/data
polybot-15m:
image: polybot/15m-trader:latest
container_name: polybot_15m
restart: unless-stopped
environment:
- POLY_API_KEY=${POLY_API_KEY}
- POLY_API_SECRET=${POLY_API_SECRET}
- POLY_PASSPHRASE=${POLY_PASSPHRASE}
- POLY_CHAIN_ID=137
- POLY_WALLET_ADDRESS=${WALLET_ADDRESS}
- POLYGON_RPC_URL=${POLYGON_RPC_URL} # For Chainlink reads
- TELEGRAM_BOT_TOKEN=${TG_BOT_TOKEN}
- TELEGRAM_CHAT_ID=${TG_CHAT_ID}
volumes:
- ./config.yml:/app/config.yml:ro
- ./data:/app/data
# .env file (same directory as docker-compose.yml) # Never commit this file to Git — add .env to .gitignore POLY_API_KEY=a1b2c3d4-e5f6-7890-abcd-ef1234567890 POLY_API_SECRET=YourBase64EncodedSecretHere== POLY_PASSPHRASE=YourPassphraseHere WALLET_ADDRESS=0xYourPolygonWalletAddress POLYGON_RPC_URL=https://polygon-rpc.com TG_BOT_TOKEN=123456789:ABCdefGHIjklMNOpqrsTUVwxyz TG_CHAT_ID=-1001234567890
# Start both bots docker compose up -d # Check running containers docker compose ps # View live logs for 15M bot docker compose logs -f polybot-15m # Restart a single service after config change docker compose restart polybot-copy
.env, not config.yml. Add .env to your .gitignore before initialising any repo.api_secret or passphrase. If someone in Discord or Telegram claiming to be PolyBot support requests your credentials, it is a scam.
The most common error. Causes and fixes:
POLY_API_KEY, POLY_API_SECRET, and POLY_PASSPHRASE match exactly what Polymarket showed you at generation time. No extra spaces or newlines..env and restart the containers.POLY_WALLET_ADDRESS must be the Polygon checksum address (mixed case, starting with 0x) of the wallet that generated the L2 key.Ensure POLY_CHAIN_ID=137 for Polymarket mainnet. Using 80001 (Mumbai testnet) will result in 403 errors as PolyBot attempts to authenticate against production endpoints.
L2 keys do not have a time-based expiry, but Polymarket may invalidate them if unusual activity is detected. PolyBot detects a 401 during an active session and immediately sends a Telegram alert with the message "API key invalidated — re-generate and update .env". No trades will be placed until the bot is restarted with valid credentials.
If your server is in a restricted region, CLOB requests may time out. Use a VPN or a VPS in an allowed jurisdiction. Check Polymarket's terms for the list of restricted regions.
# Test CLOB connectivity from your server curl -s https://clob.polymarket.com/markets?limit=1 | python3 -m json.tool # Expected: JSON with market data # If timeout: check firewall rules and jurisdiction restrictions