Polymarket Bot Tutorial · Chapter 7 of 32
Polymarket Gamma API deep dive: /events and /markets endpoints, pagination, tag IDs (864 Tennis, 745 NBA, etc), filtering by 24h volume, rate limits, and Python and Node code samples.
What this chapter covers
Gamma is Polymarket's catalog API - it lists every event, every market, tag, image, and resolved outcome the front-end displays. The CLOB API trades; Gamma describes what is tradeable. Most bot bugs at the discovery layer come from confusing the two, or from missing the pagination contract. This chapter is the field reference for Gamma's main endpoints with the exact parameter behavior our production fetchers depend on.
- Gamma vs CLOB: when to use which
- /events endpoint anatomy
- /markets endpoint anatomy
- Tags and tag IDs (verified list)
- Filtering: active, closed, volume24hr ordering
- Pagination and limits
- Rate limits and caching
- Code: fetch top 24h-volume markets
Gamma vs CLOB: when to use which
Two different services for two different jobs.
Gamma (gamma-api.polymarket.com): catalog. Lists events, markets, tags, descriptions, images, resolved outcomes, 24-hour volume, total volume, end dates. Read-only HTTP. No authentication required for most reads. Updated continuously but eventually consistent - a market just closed may still show closed: false for a few seconds.
CLOB (clob.polymarket.com): trading + order book. Lists current best bid/ask, top-N book depth, recent trades. Authenticated for write endpoints (order placement, cancellation). Real-time WebSocket channels available for book updates.
Rule of thumb: use Gamma to find what to trade; use CLOB to trade it. A bot that reads prices from Gamma is using stale data - Gamma's price fields update less frequently than CLOB's order book. A bot that reads market metadata from CLOB is making more requests than needed.
/events endpoint anatomy
GET /events returns event-level data. An "event" is a Polymarket page; a single event may contain multiple markets (e.g. the 2024 Presidential Election event has one market per candidate).
Key fields:
slug: URL-safe identifier, stable for the life of the event.title,description: human display.endDate(ISO 8601): when the event closes.active,closed: booleans; combine in a query with?active=true&closed=falsefor live events.volume,volume24hr: USD totals.tags: array of tag objects (see tags section below).markets: array of child market objects (see/marketsanatomy).
The single most common discovery pattern: GET /events?active=true&closed=false&order=volume24hr&ascending=false&limit=100. Returns the 100 highest-volume currently-live events.
/markets endpoint anatomy
GET /markets returns market-level data. A market is one Y/N or multi-outcome contract; it lives inside an event.
Key fields:
slug: URL-safe identifier.question: the title displayed on the trading page (e.g. "Will Trump be president on January 1, 2027?").outcomes: JSON string of outcome names, e.g.'["Yes","No"]'. Always two elements for binary; more for NegRisk.outcomePrices: JSON string of current prices as decimals, e.g.'["0.62","0.38"]'. Both sides sum to ~1.0 minus spread.clobTokenIds: JSON string of ERC-1155 token IDs aligned with outcomes. These are the tokens you actually buy/sell.negRisk: boolean; true for multi-outcome sum-to-1 markets. Matters for order placement (chapter 11).
The outcomes / outcomePrices / clobTokenIds fields arrive as JSON strings, not parsed arrays - JSON-decode them before using.
Tags and tag IDs (verified list)
Tags are categorical labels (Sports, Crypto, Tennis, NBA, etc.). The verified production tag IDs for the most-used categories:
| Tag | ID | Tag | ID |
|---|---|---|---|
| Sports | 1 | NBA | 745 |
| Crypto | 21 | NFL | 450 |
| Politics | 2 | Tennis | 864 |
| Bitcoin | 100196 | Esports | 702 |
| Ethereum | 100383 | Soccer | 1059 |
| Election | 3 | EPL | 739 |
| Middle East | 1432 | UCL | 2186 |
Filter by tag with ?tag_id=745 for a specific tag, or ?tag_slug=nba using the slug. Slug-based filtering is more readable in code but slightly slower; ID-based is the production default.
Filtering: active, closed, volume24hr ordering
The four filter dimensions you will use 95% of the time.
active=true|false:trueexcludes markets that the Polymarket team has hidden from the UI.closed=true|false:falseexcludes resolved markets. The combinationactive=true&closed=falseis the most common live filter.order=volume24hr,order=volume,order=endDate: sort key. Most useful isvolume24hrfor finding currently-active markets.ascending=true|false: defaults to true on most endpoints; you almost always wantfalsefor volume orderings.
Caveat: filtering by tag_id combined with order=volume24hr and ascending=false sometimes returns an empty page when the tag has very few live markets. Always over-fetch (request more than you display) and post-filter to handle this.
Pagination and limits
The limit parameter accepts 1 to 500 per call. If you leave it off, the default is only 20 results, so always set it explicitly or you will silently miss most of the catalog. Above 500 the server caps quietly: you receive 500 with no flag telling you there was more.
Pagination is offset-based: ?limit=500&offset=500 for the second page. There is no cursor-based pagination, so concurrent inserts can cause page boundaries to shift between calls. For most bot discovery purposes this is acceptable; for archive scrapes, sort by a stable field (endDate or createdAt) and detect overlap by slug.
Practical pattern for "all live markets": fetch limit=500&order=volume24hr&ascending=false. That covers the top 500 by volume which is essentially every market with non-trivial activity. Going beyond page 1 is rarely useful - markets in the tail of the volume distribution are by definition not where the action is.
Rate limits and caching
Gamma is fronted by Cloudflare and has soft rate limits per IP. Empirical thresholds observed under production load:
- Up to ~30 req/sec from one IP sustained: fine.
- Bursts of 100+ requests per second: you occasionally get a 429 (the HTTP "Too Many Requests" response), but a retry a moment later succeeds.
- ~500 req/sec sustained: rate-limit page or temporary block (10-60s).
The published response headers include Cache-Control values of 30-60 seconds for most endpoints. Honor them - there is no benefit to re-fetching the same event 10 times in a minute. Production caching pattern: in-process LRU + TTL keyed on the full URL, 30s TTL. Saves request count and reduces latency.
For high-frequency strategies, mirror Gamma data into a local store updated by a single fetcher process; have multiple consumers read from that store. One fetcher × many consumers > many fetchers × Gamma.
Code: fetch top 24h-volume markets
Reference fetcher in three languages, returning the top 50 live markets by 24-hour volume.
Python:
import requests
r = requests.get("https://gamma-api.polymarket.com/events",
params={"active":"true","closed":"false",
"order":"volume24hr","ascending":"false","limit":50},
timeout=10)
for ev in r.json()[:50]:
print(ev["slug"], ev.get("volume24hr"))
Node:
const url = "https://gamma-api.polymarket.com/events?active=true&closed=false" +
"&order=volume24hr&ascending=false&limit=50";
const events = await fetch(url).then(r => r.json());
for (const ev of events) console.log(ev.slug, ev.volume24hr);
Curl:
curl -s "https://gamma-api.polymarket.com/events?active=true&closed=false&order=volume24hr&ascending=false&limit=50" \
| jq '.[].slug'
The Polymarket gamma /events endpoint does NOT support a free-text search parameter - adding ?q=foo or ?search=foo silently returns the default ordering. Filter by slug or tag instead.












