Where Polymarket bet/event categorization actually comes from, and the non-obvious fact that the /markets endpoint cannot supply it. Verified live 2026-06-03 against the gamma API.

The data source

Categorization comes from the gamma /events?slug=<event_slug> endpoint, which returns a tags array of {label, slug} objects. These tags are the only reliable category signal.

The /markets endpoint is useless for categorization

The /markets endpoint (used by GammaClient::market_metadata) returns category = null and no tags. Do not try to derive a bucket from market metadata — you must query /events by event slug.

Multi-slug batching works: /events?slug=a&slug=b returns both events in one call. Use this to bound request fan-out when priming the category cache.

Verified tag examples (live 2026-06-03)

Eventtags slugs
MLB gamesports, games, mlb, baseball
LA mayoral racepolitics, elections, …
IEM Cologne (CS2)iem-cologne, sports, counter-strike-2, esports, major
Crypto marketcrypto

Esports events are ALSO tagged sports

An IEM/CS2 esports event carries both esports and sports tags. Esport detection MUST take precedence over sport detection, or every esports event mis-buckets as a generic sport.

Bucket mapping precedence

The category bucket is resolved by checking tag slugs in strict precedence order (first match wins):

  1. esportesports (and friends like counter-strike-2)
  2. sportsports, games, league/sport slugs
  3. geopolitics, elections, geopolitics, world
  4. cryptocrypto
  5. other — fallback

The esport→sport ordering is load-bearing (see warning above). Geo collapses the politics/elections/geopolitics/world family into one bucket.

Where it’s used

  • run_category_sync populates the pmv2_event_category cache table from this endpoint (best-effort, bounded per-chunk tokio::timeout). See pmv2-topbets-categories-filters.
  • /topbets category filters (eq:/neq:) read the cache. Filters are inert until the cache is primed by a scheduled run.