poly-scry
v0.2.0
Published
A typed fetcher for sports prediction markets, built around the SportMarketFetcher class.
Readme
poly-scry
A TypeScript-first NPM package that fetches, parses, and normalises Polymarket sports events into clean structured data — ready to drop into a trading bot, scanner, or analytics pipeline.
poly-scry talks to the public Polymarket Gamma API and handles the usual footguns for you: JSON-serialised outcomes / outcomePrices / clobTokenIds on each child market, duplicated string-vs-number volume/liquidity fields, and sport-to-tag-id resolution.
Install
npm i poly-scry
# or
pnpm add poly-scryNo API key required — the Gamma API is public.
Quick Start
import { SportMarketFetcher } from 'poly-scry';
const fetcher = new SportMarketFetcher();
const markets = await fetcher.getMarketsBySports(['basketball', 'tennis']);
for (const m of markets) {
console.log(m.question); // "Will Djokovic win Wimbledon 2025?"
console.log(m.outcomes); // ["Yes", "No"] ← already parsed
console.log(m.outcomePrices); // [0.72, 0.28] ← already parsed
console.log(m.clobTokenIds); // ["12345...", "67890..."]
console.log(m.impliedProbability); // { Yes: 0.72, No: 0.28 }
console.log(m.spread); // 0.03
console.log(m.sport); // "tennis"
console.log(m.league); // "Wimbledon 2025"
}Examples
Fetch a single sport
import { SportMarketFetcher } from 'poly-scry';
const fetcher = new SportMarketFetcher();
const nba = await fetcher.getMarketsBySports('basketball');Fan out across multiple sports
import { SportMarketFetcher } from 'poly-scry';
const fetcher = new SportMarketFetcher();
const markets = await fetcher.getMarketsBySports(['tennis', 'cricket', 'mma']);Fetch every supported sport
import { SportMarketFetcher } from 'poly-scry';
const fetcher = new SportMarketFetcher();
const all = await fetcher.getMarketsBySports('all'); // or '*'Fetch specific leagues
import { SportMarketFetcher } from 'poly-scry';
const fetcher = new SportMarketFetcher();
// single league
const nba = await fetcher.getMarketsByLeagues('nba');
// multiple leagues
const soccer = await fetcher.getMarketsByLeagues(['epl', 'la-liga', 'serie-a', 'bundesliga']);
// all esports leagues
const esports = await fetcher.getMarketsByLeagues([
'lol', 'dota2', 'cs2', 'valorant', 'cod', 'r6', 'rocket-league',
]);Filter for trade-ready markets
import { SportMarketFetcher } from 'poly-scry';
const fetcher = new SportMarketFetcher();
const markets = await fetcher.getMarketsBySports('soccer');
const tradeable = markets.filter(
(m) => m.marketStatus === 'active' && !m.cyom && m.spread < 0.05,
);
for (const m of tradeable) {
console.log(`${m.league} ${m.question} @ ${m.lastTradePrice}`);
}Handle UnknownSportError
import { SportMarketFetcher, UnknownSportError } from 'poly-scry';
const fetcher = new SportMarketFetcher();
try {
await fetcher.getMarketsBySports('curling');
} catch (err) {
if (err instanceof UnknownSportError) {
console.error(`Sport not found: ${err.sportName}`);
console.error(`Available: ${err.availableSports.join(', ')}`);
}
}Pass a custom fetch (proxy, retries, etc.)
import { SportMarketFetcher } from 'poly-scry';
const fetcher = new SportMarketFetcher({
baseUrl: 'https://gamma-api.polymarket.com',
pageSize: 100,
activeOnly: true,
includeClosed: false,
timeoutMs: 10_000,
debug: true, // structured request/response logs
});A full end-to-end walkthrough lives at examples/basic-usage.ts — run it with:
npx tsx examples/basic-usage.tsAPI
new SportMarketFetcher(options?)
interface SportMarketFetcherOptions {
baseUrl?: string; // default: 'https://gamma-api.polymarket.com'
pageSize?: number; // events per page, default: 100
activeOnly?: boolean; // default: true
includeClosed?: boolean; // default: false
timeoutMs?: number; // per-request timeout, default: 10_000
fetch?: typeof fetch; // custom fetch (proxy, undici, etc.)
debug?: boolean; // default: false
logLevel?: 'debug' | 'info' | 'warn' | 'error';
logger?: Logger; // custom logger
}fetcher.getMarketsBySports(sports)
Resolves one or more sport names to their tag_ids and returns a flat ParsedMarket[] across all matching leagues.
fetcher.getMarketsBySports('basketball');
fetcher.getMarketsBySports(['tennis', 'cricket', 'mma']);
fetcher.getMarketsBySports('all'); // every supported sportSportInput = string | readonly string[]
Throws UnknownSportError if any input has no entry in SPORT_TAG_IDS.
Supported sports
| Sport key | Tag ID(s) |
|---|---|
| all | 1 |
| soccer | 100350 |
| esports | 64 |
| cricket | 517 |
| basketball | 28, 745, 100254, 102669 |
| hockey | 100088, 899 |
| tennis | 864 |
| rugby | 102193 |
| motorsport | 435 |
| chess | 256 |
| american-football | 450, 100351 |
| baseball | 100381, 678, 10063 |
| lacrosse | 102393 |
| mma | 683, 104084 |
Use 'all' or '*' to fetch every sport with a single tag_id=1 API call.
fetcher.getMarketsByLeagues(leagues)
Resolves one or more league short-names to their tag_ids and returns a flat ParsedMarket[].
fetcher.getMarketsByLeagues('nba');
fetcher.getMarketsByLeagues(['epl', 'la-liga', 'bundesliga']);
fetcher.getMarketsByLeagues('all'); // every leagueLeagueInput = string | readonly string[]
Throws UnknownSportError if any input has no entry in LEAGUE_TAG_IDS.
Supported leagues
LEAGUE_TAG_IDS maps short unique keys to Polymarket tag IDs. Use 'all' or '*' to fetch every league.
| Key | Tag ID | League |
|-----|--------|--------|
| epl | 82 | English Premier League |
| la-liga | 780 | La Liga |
| ucl | 100977 | UEFA Champions League |
| bundesliga | 1494 | Bundesliga |
| serie-a | 101962 | Serie A |
| ligue1 | 102070 | Ligue 1 |
| eredivisie | 101735 | Eredivisie |
| liga-portugal | 101772 | Liga Portugal |
| mls | 100100 | MLS |
| brasileirao | 102648 | Brasileirão |
| argentine-primera | 102561 | Argentine Primera |
| liga-mx | 102448 | Liga MX |
| super-lig | 102564 | Turkish Süper Lig |
| russian-premier-league | 102593 | Russian Premier League |
| ukrainian-premier-league | 103886 | Ukrainian Premier League |
| austrian-bundesliga | 104929 | Austrian Bundesliga |
| serbian-superliga | 104932 | Serbian SuperLiga |
| hungarian-league | 104933 | Hungarian League |
| irish-league | 104935 | Irish League |
| slovenian-league | 104934 | Slovenian League |
| bulgarian-league | 104936 | Bulgarian League |
| allsvenskan | 104930 | Allsvenskan (Sweden) |
| israeli-league | 104931 | Israeli League |
| eliteserien | 102651 | Eliteserien (Norway) |
| superliga | 102652 | Superliga (Denmark) |
| j1-league | 102649 | J1 League (Japan) |
| j2-league | 102770 | J2 League (Japan) |
| efl-championship | 102643 | EFL Championship |
| la-liga2 | 102866 | La Liga 2 |
| ligue2 | 102871 | Ligue 2 |
| serie-b | 102870 | Serie B |
| 2-bundesliga | 102864 | 2. Bundesliga |
| fa-cup | 102594 | FA Cup |
| efl-cup | 102595 | EFL Cup |
| coppa-italia | 102008 | Coppa Italia |
| coupe-france | 102604 | Coupe de France |
| dfb-pokal | 102154 | DFB-Pokal |
| supercopa | 103075 | Supercopa de España |
| leagues-cup | 102449 | Leagues Cup |
| uel | 101787 | UEFA Europa League |
| uecl | 102763 | UEFA Conference League |
| copa-lib | 102562 | Copa Libertadores |
| copa-sud | 102563 | Copa Sudamericana |
| world-cup | 102232 | FIFA World Cup |
| conmebol | 100787 | CONMEBOL |
| concacaf | 101280 | CONCACAF |
| uefa | 102544 | UEFA |
| caf | 102540 | CAF |
| fifa | 102539 | FIFA |
| ofc | 102566 | OFC |
| afc | 101680 | AFC |
| ccc | 104343 | CCC |
| wucl | 103885 | Women's UCL |
| nba | 745 | NBA |
| wnba | 100254 | WNBA |
| euroleague | 102669 | EuroLeague Basketball |
| ncaam | 100149 | NCAA Men's Basketball |
| ncaa | 101178 | College Basketball |
| wcbb | 102003 | Women's College Basketball |
| vtb | 104344 | VTB League |
| bbl-ger | 104345 | BBL Germany |
| aba | 104346 | ABA League |
| bsl | 104347 | BSL Turkey |
| greek-league | 104348 | Greek League |
| b-league | 104349 | Japan B.League |
| fiba-q-africa | 104350 | FIBA Qualifiers Africa |
| fiba-q-americas | 104351 | FIBA Qualifiers Americas |
| fiba-q-asia | 104352 | FIBA Qualifiers Asia |
| fiba-q-europe | 104353 | FIBA Qualifiers Europe |
| nhl | 899 | NHL |
| khl | 102908 | KHL |
| ahl | 102907 | AHL |
| shl | 102906 | SHL (Sweden) |
| del | 102909 | DEL (Germany) |
| czech-extraliga | 102910 | Czech Extraliga |
| swiss-nl | 100642 | Swiss National League |
| iihf-wc | 102151 | IIHF World Championship |
| olympic-hockey-m | 103666 | Men's Olympic Hockey |
| olympic-hockey-w | 103667 | Women's Olympic Hockey |
| nfl | 450 | NFL |
| cfb | 100351 | College Football |
| mlb | 100381 | MLB |
| wbc | 10063 | World Baseball Classic |
| zuffa | 683 | Zuffa / MMA |
| power-slap | 104084 | Power Slap |
| pll | 102392 | Premier Lacrosse League |
| wll | 103911 | Women's Lacrosse League |
| wtt | 103767 | World Table Tennis (Men) |
| mlp | 102471 | Major League Pickleball |
| lol | 65 | League of Legends |
| dota2 | 102366 | Dota 2 |
| cs2 | 100780 | Counter-Strike 2 |
| valorant | 101672 | Valorant |
| cod | 100230 | Call of Duty |
| mlbb | 102750 | Mobile Legends |
| eafc | 102751 | EA FC / FIFA Esports |
| overwatch | 102753 | Overwatch |
| pubg | 102754 | PUBG |
| r6 | 102755 | Rainbow Six Siege |
| rocket-league | 102756 | Rocket League |
| hok | 102757 | Honor of Kings |
| sc2 | 102758 | StarCraft II |
| scbw | 102759 | StarCraft: Brood War |
| wild-rift | 102752 | Wild Rift |
| ipl | 101977 | Indian Premier League |
| bbl | 102813 | Big Bash League |
| sa20 | 102814 | SA20 |
| t20 | 102810 | T20 Cricket |
| odi | 102815 | ODI Cricket |
| test | 102842 | Test Cricket |
| lpl | 102812 | Lanka Premier League |
| psl-cricket | 102811 | Pakistan Super League |
| intl-cricket | 102803 | International Cricket |
| aus-cricket | 102946 | Australia Cricket |
| eng-cricket | 102944 | England Cricket |
| nz-cricket | 102949 | New Zealand Cricket |
| ind-cricket | 102943 | India Cricket |
| sa-cricket | 102947 | South Africa Cricket |
| pak-cricket | 102945 | Pakistan Cricket |
| uae-cricket | 102948 | UAE Cricket |
| ban-cricket | 103014 | Bangladesh Cricket |
| u19-wc | 103178 | U19 World Cup |
| wt20 | 103179 | Women's T20 |
| wncl | 103180 | Women's NCL |
| wt20-wcq | 103183 | Women's T20 WCQ |
| afg-t20 | 103181 | Afghanistan T20 |
| t20-asia | 103182 | T20 (Bahrain/Malaysia/HK) |
| hk-t20 | 104072 | Hong Kong T20 |
| pak-t20-cup | 104073 | Pakistan T20 Cup |
| nepal-t20 | 104204 | Nepal T20 |
| t10-thunder | 104206 | Thunderbolt T10 |
| llc | 104203 | Legends League Cricket |
| t10 | 104447 | T10 |
| psl | 103805 | PSL |
| cpl | 103808 | CPL |
| bpl-cricket | 103809 | BPL |
| smat | 103810 | SMAT |
| ilt20 | 103812 | ILT20 |
| mlc-cricket | 103813 | MLC |
| csa-t20 | 102808 | Cricket South Africa |
| sheffield-shield | 102817 | Sheffield Shield |
| rugby | 102193 | Rugby |
| prem-rugby | 103054 | Premiership Rugby |
| top14 | 103055 | Top 14 |
| six-nations | 103111 | Six Nations |
| champions-cup | 103113 | European Champions Cup |
| urc | 103112 | United Rugby Championship |
| super-rugby | 103114 | Super Rugby Pacific |
| currie-cup | 103115 | Currie Cup |
Types
The most important ones — see src/types.ts for the full list.
type MarketStatus = 'active' | 'closed' | 'archived' | 'pending' | 'unknown';
interface ParsedMarket {
// identity
id: string;
question: string;
slug: string;
// injected by poly-scry
sport: string;
eventId: string;
eventTitle: string;
league?: string;
impliedProbability: Record<string, number>;
marketStatus: MarketStatus;
// outcomes — already parsed, not strings
outcomes: string[];
outcomePrices: number[];
clobTokenIds: string[];
// pricing & orderbook
lastTradePrice: number;
bestBid: number;
bestAsk: number;
spread: number;
// volume / liquidity — normalised to numbers
volume: number;
volume24hr: number;
liquidity: number;
}Errors
All thrown errors extend PolymarketSportsError.
| Error | When | Extra fields |
|---|---|---|
| UnknownSportError | Sport or league name not found | sportName, availableSports |
| FetchError | Non-2xx from Gamma API | statusCode, url |
| ParseError | JSON.parse fails on a market field | field, marketId, raw |
| TimeoutError | Request exceeds timeoutMs | url, timeoutMs |
import {
SportMarketFetcher,
PolymarketSportsError,
UnknownSportError,
FetchError,
ParseError,
TimeoutError,
} from 'poly-scry';Project structure
src/
index.ts # public exports
fetcher.ts # SportMarketFetcher class
parser.ts # raw → ParsedMarket / ParsedSportEvent
sports-resolver.ts # sport name → tagId
league-resolver.ts # league name → tagId
paginator.ts # offset-paginator for Gamma endpoints
errors.ts # error classes
logger.ts # structured logger
types.ts # TypeScript interfaces
examples/
basic-usage.ts # end-to-end walkthrough
tests/
parser.test.ts
resolver.test.ts
league-resolver.test.ts
fetcher.integration.test.tsScripts
npm run build— compile TypeScript todist/npm run typecheck— type-check without emittingnpm test— run the test suite oncenpm run test:watch— run the test suite in watch mode
Status
The public API is still settling. SportMarketFetcher.getMarketsBySports and SportMarketFetcher.getMarketsByLeagues are the stable entry points.
