Table of Contents
Building a trading bot for Polymarket is not just "call an API and buy Yes." A useful bot needs market discovery, real-time prices, a strategy, risk limits, execution controls, storage, monitoring, and a way to stop itself when data gets stale.
This guide walks through the practical architecture: how to use the Polymarket API, how to design a bot, what to host, and how the same structure can extend to Kalshi and Pariflow.
It is written for builders, not for hype. Prediction markets are risky. Automated trading can lose money quickly, especially in thin markets or during breaking news. Start read-only, then paper trade, then use small live limits.
Core Trading Bot Stack
A production-ready prediction market bot usually has six parts.
| Layer | What it does | Common failure |
|---|---|---|
| Market scanner | Finds active markets worth tracking | Trades stale, low-liquidity, or closed markets |
| Data adapter | Reads market metadata, books, trades, and positions | Mixes inconsistent venue formats |
| Signal engine | Estimates fair probability and edge | Confuses price movement with value |
| Risk engine | Limits size, exposure, loss, and stale data | Lets one bug scale across many markets |
| Execution engine | Places, cancels, and reconciles orders | Creates partial-fill or duplicate-order risk |
| Monitoring | Logs decisions and alerts humans | Fails silently while money is at risk |
For Polymarket specifically, the main API layers are:
- Gamma API: market discovery, events, tags, comments, sports metadata, search, and public profiles.
- Data API: positions, trades, user activity, holders, open interest, leaderboards, and analytics.
- CLOB API: order book data, pricing, midpoints, spreads, price history, order placement, and cancellations.
- WebSocket channels: live market, user, and sports updates when polling is too slow.
The official Polymarket docs describe these API domains and their current base URLs: https://gamma-api.polymarket.com, https://data-api.polymarket.com, and https://clob.polymarket.com.
First, Decide What Kind of Bot You Are Building
Do not start with code. Start with behavior.
A bot that "trades everything" is usually just an expensive bug. A first bot should do one narrow job well.
Good first bot types
| Bot type | What it does | Why it is a good start |
|---|---|---|
| Alert bot | Watches prices and sends opportunities to Telegram, Slack, or email | Read-only, low-risk, easy to validate |
| Spread monitor | Finds markets with wide bid-ask spreads or thin books | Teaches market microstructure before execution |
| Cross-venue monitor | Compares similar events across Polymarket, Kalshi, and Pariflow | Useful for research and possible arbitrage alerts |
| Paper trader | Records hypothetical orders and simulated fills | Lets you test strategy quality before live trading |
| Small market maker | Posts passive orders inside strict limits | Teaches execution but requires real risk controls |
| Portfolio risk bot | Watches current exposure and warns when positions overlap | Safer than fully automated trading |
For most teams, the right path is:
- Build a scanner.
- Add alerts.
- Add paper trading.
- Add manual approval.
- Add limited live execution only after the first four steps are reliable.
Polymarket API Overview
Polymarket exposes several API surfaces. Your bot should not treat them as one giant endpoint.
Gamma API for Market Discovery
Use the Gamma API when the bot needs to answer questions like:
- Which markets are active?
- Which event does this market belong to?
- What are the outcomes?
- When does the market close?
- Is the market order-book enabled?
- What category or tag does this market belong to?
Example use cases:
- sync all active politics markets once every few minutes
- search for markets containing a specific keyword
- pull outcome labels and token IDs before subscribing to an order book
- ignore markets that are closed, inactive, or not order-book enabled
Basic TypeScript example:
type PolymarketMarket = {
id: string
question: string
slug?: string
active?: boolean
closed?: boolean
enableOrderBook?: boolean
endDate?: string
outcomes?: string
outcomePrices?: string
}
export async function findPolymarketMarkets(query: string) {
const url = new URL("https://gamma-api.polymarket.com/markets")
url.searchParams.set("closed", "false")
url.searchParams.set("limit", "25")
url.searchParams.set("q", query)
const response = await fetch(url)
if (!response.ok) {
throw new Error(`Gamma API failed with ${response.status}`)
}
const markets = (await response.json()) as PolymarketMarket[]
return markets.filter((market) => {
return market.active && !market.closed && market.enableOrderBook
})
}
This is market discovery, not execution. Store the result in your database so your worker does not repeatedly rediscover the same markets during every loop.
CLOB API for Prices, Books, and Trading
The CLOB API is where trading logic gets serious. It includes public endpoints for order book and pricing data, plus authenticated endpoints for order management.
Your bot will commonly need:
- best bid and best ask
- midpoint price
- spread
- full order book depth
- last trade price
- historical price data
- current open orders
- order placement
- order cancellation
Keep CLOB access behind a small adapter:
export type NormalizedBookLevel = {
price: number
size: number
}
export type NormalizedOrderBook = {
venue: "polymarket"
marketId: string
tokenId: string
bids: NormalizedBookLevel[]
asks: NormalizedBookLevel[]
bestBid: number | null
bestAsk: number | null
spread: number | null
asOf: string
}
export interface VenueAdapter {
searchMarkets(query: string): Promise<unknown[]>
getOrderBook(tokenId: string): Promise<NormalizedOrderBook>
getPositions(): Promise<unknown[]>
previewOrder(input: TradeSignal): Promise<QuotePreview>
placeOrder(input: TradeSignal): Promise<OrderResult>
cancelOrder(orderId: string): Promise<void>
}
Why normalize early? Because later you may want the same strategy to compare Polymarket with Kalshi or Pariflow. Your strategy should not care whether a price came from a crypto-native CLOB, a regulated event-contract exchange, or a Pariflow internal market feed.
WebSockets for Live Updates
Polling is fine for a scanner. It is often too slow for execution.
Use WebSockets when you need:
- faster order book updates
- user order/fill updates
- live sports market changes
- lower-latency market-making decisions
Do not assume the WebSocket stream will always be clean. Add:
- reconnect logic
- stale data detection
- subscription tracking
- heartbeat checks
- fallback polling for reconciliation
The bot should stop trading if the book is stale, if reconnects are failing, or if the latest book timestamp is outside your tolerance.
Suggested Project Structure
For a Node.js or TypeScript bot:
polymarket-bot/
src/
adapters/
polymarket.ts
kalshi.ts
pariflow.ts
strategy/
fair-value.ts
cross-venue.ts
market-making.ts
risk/
limits.ts
exposure.ts
kill-switch.ts
execution/
paper-broker.ts
live-broker.ts
reconciler.ts
storage/
db.ts
schema.ts
alerts/
telegram.ts
slack.ts
worker.ts
.env.example
Dockerfile
docker-compose.yml
You can build the same thing in Python. The architectural split matters more than the language.
Environment Variables
Never hard-code private keys, API keys, or webhook URLs.
Use a .env.example like this:
BOT_MODE="paper"
POLYMARKET_HOST="https://clob.polymarket.com"
POLYMARKET_PRIVATE_KEY="0x..."
POLYMARKET_FUNDER_ADDRESS="0x..."
DATABASE_URL="postgresql://user:password@localhost:5432/bot"
REDIS_URL="redis://localhost:6379"
MAX_ORDER_USD="25"
MAX_MARKET_EXPOSURE_USD="100"
MAX_DAILY_LOSS_USD="150"
MAX_SPREAD_BPS="600"
STALE_BOOK_MS="5000"
ALERT_WEBHOOK_URL="https://hooks.slack.com/..."
KILL_SWITCH_FILE="/tmp/polymarket-bot.kill"
Use different keys for development, paper mode, and live mode. If a platform supports limited API scopes, do not give a read-only scanner trading permissions.
Build the Bot Loop
A simple bot loop should do four things:
- Pull or receive market data.
- Generate a signal.
- Pass the signal through risk checks.
- Record or execute the decision.
type TradeSignal = {
venue: "polymarket"
marketId: string
tokenId: string
side: "BUY" | "SELL"
limitPrice: number
sizeUsd: number
fairPrice: number
edgeBps: number
reason: string
}
export async function runBotTick() {
const markets = await scanner.findCandidateMarkets()
for (const market of markets) {
const book = await polymarket.getOrderBook(market.tokenId)
const signal = await strategy.evaluate({ market, book })
if (!signal) continue
const riskDecision = await risk.check(signal)
if (!riskDecision.allowed) {
await logger.info("signal_skipped", {
signal,
reason: riskDecision.reason,
})
continue
}
if (process.env.BOT_MODE === "paper") {
await paperBroker.record(signal)
continue
}
await execution.placeLimitOrder(signal)
}
}
The important part is not the syntax. The important part is that every skipped order has a reason, and every live order passed the same risk gate.
Strategy Design: Start With Probabilities, Not Vibes
A prediction-market bot needs a view of fair probability.
That view can come from:
- a statistical model
- manually curated probabilities
- news signals
- sports data
- crypto price data
- cross-venue prices
- volatility and liquidity filters
- a human approval queue
A simple signal rule might be:
function evaluateEdge(input: {
fairPrice: number
bestAsk: number | null
minEdgeBps: number
}) {
if (input.bestAsk === null) return null
const edge = input.fairPrice - input.bestAsk
const edgeBps = Math.round(edge * 10_000)
if (edgeBps < input.minEdgeBps) return null
return {
side: "BUY" as const,
limitPrice: input.bestAsk,
edgeBps,
}
}
This is intentionally simple. Real strategies need more guards:
- ignore markets close to settlement unless the strategy is built for resolution risk
- require minimum liquidity
- avoid very wide spreads
- avoid markets where the rules are ambiguous
- reduce size during breaking news
- avoid duplicate exposure across correlated markets
Risk Controls That Matter
Most bot failures are not prediction failures. They are risk-control failures.
Use hard limits:
| Limit | Example |
|---|---|
| Max order size | Never place an order above $25 in early live testing |
| Max market exposure | Never risk more than $100 on one market |
| Max category exposure | Cap politics, crypto, sports, or macro separately |
| Max daily loss | Stop trading for the day after a fixed drawdown |
| Max stale book age | Do not trade if the book is older than 5 seconds |
| Max spread | Do not cross an extremely wide spread |
| Max open orders | Avoid hundreds of forgotten live orders |
Add a kill switch:
import fs from "node:fs/promises"
export async function killSwitchEnabled(path = "/tmp/polymarket-bot.kill") {
try {
await fs.access(path)
return true
} catch {
return false
}
}
export async function assertCanTrade() {
if (await killSwitchEnabled()) {
throw new Error("Kill switch enabled")
}
}
The kill switch should cancel open orders and stop new orders. It should be easy for a non-developer operator to trigger.
Paper Trading Before Live Trading
Paper trading is not optional.
Run the bot in paper mode long enough to learn:
- how often it generates signals
- how often signals disappear before they could fill
- how much slippage you would have paid
- which markets create most errors
- whether alerts fire when they should
- whether the strategy still works after fees and spread
Paper fills should be conservative. If you assume every order fills at the midpoint, you are not testing a bot. You are testing a fantasy.
Better paper-fill logic:
- marketable order: fill at visible best ask or bid
- passive order: fill only if the market trades through your price
- stale book: no fill
- insufficient depth: partial fill
Hosting Options
A trading bot is usually a worker, not a web page.
You can host the dashboard on Vercel, but the bot itself is usually better as an always-on process.
Good hosting choices
| Host type | Best for | Notes |
|---|---|---|
| VPS | Maximum control | Use Docker Compose, systemd, and direct logs |
| Fly.io or Railway worker | Simple container deployment | Good for always-on processes |
| Render background worker | Easy managed deployment | Watch sleep and restart behavior |
| Google Cloud Run jobs/workers | Cloud-native teams | Good secrets and logging |
| Kubernetes | Larger trading systems | Overkill for a first bot |
Dockerfile
FROM node:22-slim AS base
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY . .
RUN npm run build
CMD ["node", "dist/worker.js"]
Docker Compose
services:
bot:
build: .
restart: unless-stopped
env_file: .env
depends_on:
- postgres
- redis
postgres:
image: postgres:16
restart: unless-stopped
environment:
POSTGRES_PASSWORD: change-me
volumes:
- pgdata:/var/lib/postgresql/data
redis:
image: redis:7
restart: unless-stopped
volumes:
pgdata:
Monitoring Checklist
Track these metrics:
- last successful market sync
- last WebSocket message time
- open order count
- order placement errors
- cancellation errors
- current exposure
- daily realized and unrealized P&L
- stale book skips
- risk gate rejections
- process restarts
Send alerts when:
- the worker restarts repeatedly
- the WebSocket disconnects
- order placement fails
- open orders exceed your limit
- daily loss limit is reached
- the database becomes unavailable
- a reconciliation job finds unknown orders
Adapting the Bot to Kalshi
Kalshi is a different venue, not just another endpoint.
The architecture can transfer, but the adapter must be separate. Kalshi has its own REST and WebSocket API environment URLs, demo and production environments, and authenticated requests signed with a private key. Its docs also distinguish production and demo credentials, so do not mix keys across environments.
What can stay shared:
- strategy engine
- risk limits
- logging
- monitoring
- normalized market model
- paper trading logic
- cross-venue comparison UI
What must be venue-specific:
- authentication
- market IDs and tickers
- order model
- order book format
- settlement rules
- fees and position accounting
- rate limits
- compliance and geography rules
A useful Kalshi adapter shape:
export class KalshiAdapter implements VenueAdapter {
async searchMarkets(query: string) {
// Call Kalshi market endpoints and normalize result shape.
}
async getOrderBook(ticker: string) {
// Normalize bid and ask levels into your internal format.
}
async placeOrder(signal: TradeSignal) {
// Sign the request with the Kalshi key and private key.
}
}
Kalshi is especially useful for regulated U.S. event contracts, but that also means you should read the contract rules carefully. A market can look similar to a Polymarket market while resolving under different terms.
Adapting the Bot to Pariflow API, MCP, and CLI
Pariflow is a strong fit for research and workflow tooling because it can expose market search, live odds, order books, portfolio reads, and quote previews through developer interfaces.
There are three useful integration modes:
| Pariflow tool | Bot use case |
|---|---|
| API | Product integrations, dashboards, internal services, and backend workers |
| MCP | Agent workflows, research assistants, market analysis, and guided operator tools |
| CLI | Local smoke tests, market inspection, order book checks, and operational scripts |
The Pariflow CLI can be used for developer workflows like:
npm install -g @pariflow/cli
export PARIFLOW_API_KEY="pf_live_..."
pariflow mcp ping
pariflow tools list --table
pariflow market search "F1" --table
pariflow quote will-team-win --side buy --outcome yes --amount 100
The current Pariflow CLI is intentionally preview-oriented: it can inspect tools, search markets, and preview quotes, but it should not be treated as a blind live-execution button.
That separation is useful. It lets teams build agent and operator workflows where AI can research, summarize, and preview, while a human or controlled backend service owns final execution.
Five Bot Examples Worth Building
1. Cross-Venue Probability Monitor
This bot tracks the same or similar events across Polymarket, Kalshi, and Pariflow.
It can:
- normalize market titles and rules
- compare implied probabilities
- flag large gaps
- show liquidity and spread side by side
- send alerts when the gap is large enough to review
This is often better than a fully automated arbitrage bot because it helps humans catch real opportunities without pretending every market is perfectly equivalent.
2. Resolution Risk Watchdog
This bot watches markets near settlement.
It can:
- track close dates
- read resolution rules
- compare official source updates
- flag ambiguous markets
- warn before an order is placed close to resolution
This is useful because many bad trades happen when a trader understands the headline but not the settlement text.
3. Passive Market-Making Bot
This bot posts limit orders around a fair value estimate.
It can:
- quote only when spread is wide enough
- cancel when news breaks
- reduce size near close
- skip low-depth markets
- cap inventory per side
Market making is harder than it looks. Start tiny and expect partial fills.
4. News-to-Market Assistant
This bot does not need to trade automatically.
It can:
- ingest trusted news feeds
- map news to related markets
- summarize what changed
- estimate which side should move
- create an operator review card
This works well with MCP-style workflows because an agent can gather context and prepare a note without being allowed to place trades.
5. Portfolio Exposure Bot
This bot watches your positions.
It can:
- detect correlated exposure
- warn if one news event affects many markets
- reduce duplicate risk
- flag stale positions
- suggest exits when liquidity disappears
Many traders need this more than another entry signal.
Go-Live Checklist
Before switching from paper to live:
- Run read-only mode for several days.
- Run paper mode with realistic fill rules.
- Review logs for skipped and accepted signals.
- Confirm open orders reconcile correctly after restart.
- Confirm kill switch cancels orders.
- Set tiny live limits.
- Add alerting for stale data and order failures.
- Keep secrets outside the repo.
- Keep venue adapters separate.
- Read the market rules before trading a new category.
If the bot cannot explain why it placed an order, it should not place the order.
Useful API References
- Polymarket API documentation for the current API index and endpoint families.
- Polymarket market data overview for Gamma, CLOB, and Data API roles.
- Polymarket CLOB client reference for L2 client methods and authenticated CLOB workflows.
- Kalshi API environments for production and demo REST/WebSocket URLs.
- Kalshi API keys for request signing and key management.
Final Takeaway
The best first Polymarket bot is not a black-box trading machine. It is a disciplined system that reads markets, normalizes data, explains signals, rejects risky trades, logs decisions, and only executes when the setup is clear.
Build it in layers:
- Scanner.
- Alerts.
- Paper trading.
- Human approval.
- Tiny live execution.
- Multi-venue adapters.
Once that foundation is solid, you can extend the same bot architecture to Kalshi and Pariflow without rewriting the strategy from scratch.