PredictorSDK
SDK

Python SDK Reference

Type reference for the PredictorSDK Python client.

The Python client is auto-generated from the OpenAPI spec and published as predictorsdk on PyPI.

Client surfaces

SurfaceEndpoint
client.get_plans()GET /v1/plans (no auth required)
client.get_sports_matching_markets(...)GET /v1/matching-markets/sports
client.lookup_sports_matching_markets(...)GET /v1/matching-markets/sports/lookup
client.get_markets(...)GET /v1/markets
client.get_categories()GET /v1/categories
client.get_market(market_id, ...)GET /v1/markets/{market_id}
client.get_event(event_id, ...)GET /v1/events/{event_id}
client.get_binance_crypto_prices(...)GET /v1/crypto-prices/binance
client.get_polymarket_wallet(...)GET /v1/polymarket/wallet
client.list_polymarket_wallet_positions(...)GET /v1/polymarket/wallet/positions

The SDK provides both synchronous (PredictorSDK) and asynchronous (AsyncPredictorSDK) clients.

Four surfaces are paginated with a cursor: get_sports_matching_markets, get_markets, get_binance_crypto_prices and list_polymarket_wallet_positions. Each returns a pager that walks the cursor for you; see Pagination. The other surfaces return the parsed response.

Sync client

from predictorsdk import PredictorSDK

client = PredictorSDK(token="YOUR_API_KEY")

# A paginated call returns a pager: pager.items holds this page's events.
pager = client.get_sports_matching_markets(league="nfl")
for event in pager.items:
    print(event.event_id, event.scheduled_date, event.scheduled_start, event.title)

# A lookup finds events by identifier and answers one unpaginated response.
found = client.lookup_sports_matching_markets(source_id="kalshi:KXNBAGAME-26OCT20OKCSAS")

categories = client.get_categories()
plans = client.get_plans()  # Public: works without an API key.

Async client

import asyncio
from predictorsdk import AsyncPredictorSDK

client = AsyncPredictorSDK(token="YOUR_API_KEY")

async def main():
    pager = await client.get_sports_matching_markets(league="nfl")
    async for event in pager:  # every event on every page
        print(event.event_id, event.title)

asyncio.run(main())

Pagination

A call to get_sports_matching_markets, get_markets, get_binance_crypto_prices or list_polymarket_wallet_positions returns a pager. Iterate it to visit every item on every page; the client sends each next cursor and repeats the filters that issued it. On the async client, await the call, then use async for.

# Every event on every page.
for event in client.get_sports_matching_markets(league="nfl", limit=50):
    print(event.event_id, event.title)

# One provider's markets, a page at a time. The whole catalog is about
# 470,000 markets, thousands of pages, so filter by provider or category first.
pager = client.get_markets(provider="prophetx", limit=100)
for page in pager.iter_pages():
    print(len(page.items), page.response.pagination.total)
MemberDescription
pager.itemsThe items on this page, as a list
pager.responseThis page's whole parsed response, such as snapshot and pagination
pager.has_nextTrue while pagination.next_cursor is set
pager.next_page()The next page's pager, or None after the last page
pager.iter_pages()The pager of every page in turn, starting with this one
for item in pagerEvery item on every page (async for on the async client)

A pager is immutable: next_page() and iter_pages() return new pagers. Iterating the pager itself walks every page, so use pager.items when you want only the first. pagination.total is an integer on get_markets and get_sports_matching_markets, and None on get_binance_crypto_prices and list_polymarket_wallet_positions, whose upstreams cannot count; follow has_next rather than total. See the Pagination guide for the cursor rules.

Configuration

ParameterTypeDescription
tokenstrYour API key (Bearer token)
base_urlstr | NoneOverride the base URL
environmentPredictorSDKEnvironmentAPI environment (defaults to PRODUCTION)
timeoutfloat | NoneRequest timeout in seconds (default: 60)
max_retriesint | NoneRetries after a 408, 429 or 5xx response, or a connection that failed before any response (default: 2)
httpx_clienthttpx.Client | NoneCustom httpx client instance

Key types

These tables describe what the API promises. A response that leaves a field out still parses, and the field reads None; see Responses that leave a field out.

SportsMatchingListResponse

The pager.response of get_sports_matching_markets. Contains the matched events in data, the pagination block, and a snapshot freshness block.

FieldTypeDescription
dataList[CanonicalSportsEvent]Matched events, soonest scheduled start first, each once. Each carries its full-game moneyline, or every matched submarket with include_submarkets=True
paginationPaginationBlockPagination metadata
snapshotSportsMatchingSnapshotMatching freshness. observed_at is when the oldest venue read behind the snapshot began, or None when unknown — check it before treating a missing event as unmatched

SportsMatchingLookupResponse

The response from lookup_sports_matching_markets: one unpaginated response, with no pagination block.

FieldTypeDescription
dataList[CanonicalSportsEvent]The events the identifiers found, soonest scheduled start first, each once, with every matched submarket unless include_submarkets=False
lookupsDict[str, SportsMatchingLookupResult]Every event_id and source_id you sent, as sent, with the events it found
snapshotSportsMatchingSnapshotMatching freshness, as above

SportsMatchingLookupResult

FieldTypeDescription
event_idsList[str]The event_ids of the events in data that hold the identifier; empty when it found none

CanonicalSportsEvent

One matched game, as the list and the lookup both return it.

FieldTypeDescription
event_idstrThe canonical event key, such as nba-okc-sas-2026-10-20
sportstrThe sport, such as basketball
leaguestrThe league: nba, wnba, nfl, mlb or nhl
titlestrThe event title, such as Oklahoma City Thunder vs. San Antonio Spurs
scheduled_startdatetime | NoneWhen the game starts, when a venue published an exact start time; None when venues publish only a date. Millisecond precision, in UTC
scheduled_datedateThe game's calendar day in America/New_York. Always present, even when scheduled_start is None
participantsList[CanonicalSportsParticipant]The two sides, each with a key, a name and a role (away or home)
submarketsList[CanonicalSportsSubmarket]The matched submarkets in reading order: the full game, then each half and quarter, and within each the moneyline, spreads, totals, team totals and props

PaginationBlock

FieldTypeDescription
limitintEchoes the limit request param (default 25, max 100)
totalint | NoneTotal matching items across all pages; None on the routes that cannot count (wallet positions, crypto prices)
has_moreboolTrue when more pages exist
next_cursorstr | NoneThe cursor for the next page. A pager sends it for you; pass it back as cursor only to resume a walk by hand

CanonicalSportsSourceMarket

One venue market matched to a canonical submarket, with the same fields on every venue. event_id is the venue's parent event, for get_event; market_id is the market itself, for get_market. Pass provider to either call as its provider, or use the composite f"{provider}:{id}". Any of event_id, market_id or market_slug, prefixed with provider, finds the event again as source_id. See Which identifier goes where.

FieldTypeDescription
providerCanonicalSportsSourceMarketProviderkalshi, polymarket, predict, sxbet, alpha-arcade, prophetx, pred, or limitless
event_idstrThe venue's parent event, for get_event. Not a market ID
market_idstrThe market, for get_market. Kalshi lists a game winner as one market per team, so it appears once per team
market_namestrThe market's name: the venue's own title, or on SX Bet its two outcome labels, as get_market titles it
market_slugstr | NoneThe venue's slug, where it has one (Polymarket, AlphaArcade). source_id accepts it; for get_market send market_id
outcomesList[CanonicalSportsSourceOutcome]Each side's outcome_id and label, mapped to a canonical outcome; get_market returns the same IDs. Kalshi and Pred sides are yes/no, labelled Yes/No

get_sports_matching_markets parameters

All keyword arguments, all optional. The filters narrow the list, and total, has_more and the cursors describe the filtered set.

ParameterTypeDescription
limitint | NoneEvents per page (1–100, default 25)
cursorstr | NoneResumes a walk by hand; a pager sends it for you. Valid only with the filters that issued it
leagueGetSportsMatchingMarketsRequestLeague | NoneOnly this league: "nba", "wnba", "nfl", "mlb" or "nhl"
scheduled_datedatetime.date | NoneOnly the games on this day in America/New_York: the event's scheduled_date
participantstr | NoneOnly events with a participant whose key is this (such as "okc"), case-insensitive. Keys are unique only within a league (la is the Rams in the NFL and the Sparks in the WNBA), so pair it with league
include_settledbool | NoneAlso read the settled archive: games whose date has passed, and cancelled games (default False)
include_submarketsbool | NoneEvery matched submarket, or only the full-game moneyline (default False)
player_prop_matchstr | NonePlayer-prop policy (strict or same_prop); requires include_submarkets=True
from datetime import date

# Tonight's NFL games: filters compose, and every page keeps them.
for event in client.get_sports_matching_markets(league="nfl", scheduled_date=date(2026, 10, 15)):
    print(event.event_id, event.scheduled_start)

lookup_sports_matching_markets parameters

Send at least one identifier; with both, at most 100 unique values in all.

ParameterTypeDescription
event_idstr | Sequence[str] | NoneCanonical event key(s)
source_idstr | Sequence[str] | NoneVenue ID(s) as {provider}:{id}: any market_id, market_slug or event_id a source market publishes, a Polymarket, Predict or AlphaArcade outcome token, or a Polymarket condition ID. Case-insensitive
include_settledbool | NoneAlso search the settled archive: games whose date has passed, and cancelled games (default False)
include_submarketsbool | NoneEvery matched submarket (default True for a lookup), or only the full-game moneyline
player_prop_matchstr | NonePlayer-prop policy (strict or same_prop); requires include_submarkets=True, the default for a lookup
# `lookups` reports what each identifier found; there is no pagination block.
response = client.lookup_sports_matching_markets(
    source_id=["kalshi:KXNBAGAME-26OCT20OKCSAS", "sxbet:L19766755"]
)
for identifier, found in response.lookups.items():
    print(identifier, found.event_ids)

MarketsListResponse

The pager.response of get_markets. Contains data, catalog snapshot freshness, and pagination.

FieldTypeDescription
dataList[UnifiedMarket]Array of markets for the current page
snapshotMarketsSnapshotCatalog freshness; observed_at is when the crawl behind the oldest provider snapshot in the bound page scope began
paginationPaginationBlockPagination metadata

UnifiedMarket

A market from any supported prediction market provider.

FieldTypeDescription
idstrComposite ID in the format {provider}:{provider_id}
providerUnifiedMarketProviderkalshi, polymarket, predict, sxbet, hyperliquid, alpha-arcade, prophetx, limitless, or pred
titlestrHuman-readable market title/question
categoryMarketCategoryCanonical top-level category, such as sports, politics, or crypto

get_markets parameters

ParameterTypeDescription
limitint | NoneResults per page (1–100, default 25)
cursorstr | NoneResumes a walk by hand; a pager sends it for you
categoryMarketCategory | NoneOptional canonical category filter
providerGetMarketsRequestProvider | NoneOptional canonical provider filter; replay cursors with the same value

get_market and get_event parameters

get_market(market_id, ...) and get_event(event_id, ...) take the ID as their first argument, either platform-native or the composite {provider}:{id}. Both take an optional provider keyword (GetMarketRequestProvider and GetEventRequestProvider) to say which venue an ID belongs to. A bare ID that more than one venue holds raises ConflictError with the candidates; retry with provider set to one of them, or with the composite form. The retired platform is not accepted.

market = client.get_market("KXNBAGAME-26OCT20OKCSAS-OKC", provider="kalshi")
event = client.get_event("KXNBAGAME-26OCT20OKCSAS", provider="kalshi")
print(event.provider)  # "kalshi": pass it back as `provider` for the nested markets

EventResponse

The response from get_event.

FieldTypeDescription
event_idstrEcho of the event ID you sent
providerEventResponseProviderThe venue the ID resolved against, the value to pass as provider to get_market for its nested markets
titlestrThe event title
marketsList[EventMarket]The markets nested under the event, each with a platform-native market_id and a title
fanoutEventFanout | NonePresent when Kalshi sibling events were merged in; lists which siblings were attempted and which returned nothing

get_binance_crypto_prices parameters

ParameterTypeDescription
currencystrBinance trading pair, such as btcusdt
start_timeint | NoneStart of the range, a Unix timestamp in milliseconds (inclusive)
end_timeint | NoneEnd of the range, a Unix timestamp in milliseconds (inclusive)
limitint | NonePrices per page, 1–100 (default 100 with a time range); more than 100 is a 400
cursorstr | NoneResumes a walk by hand; a pager sends it for you. The retired pagination_key is not accepted

CryptoPricesResponse

The pager.response of get_binance_crypto_prices: newest prices first, one per second.

FieldTypeDescription
dataList[CryptoPriceItem]The prices on this page
paginationPaginationBlockPagination metadata; total is None

Raw responses

Use with_raw_response on a surface that is not paginated to access HTTP metadata alongside the parsed body:

raw = client.with_raw_response.lookup_sports_matching_markets(
    source_id="kalshi:KXNBAGAME-26OCT20OKCSAS"
)
print(raw.status_code)
print(raw.headers)
print(len(raw.data.data))

A paginated surface returns its pager from with_raw_response too, and a pager carries no status or headers; for those, send the request through your own httpx_client and read its responses there.

Responses that leave a field out

The client reads a response without validating it against the spec, as the TypeScript and Go clients do. A field the spec marks required that a response does not carry reads None instead of raising an error, so a call against an API that is a release ahead of or behind your SDK still returns everything the response did carry. Older SDK releases raised a ParsingError in this case.

  • A value of an unexpected type is passed through as the API sent it.
  • A field the SDK does not know is kept as an attribute on its model.
  • Dates and datetimes are still parsed to date and datetime, and nested objects are still the generated models, so isinstance checks and attribute access work as before.

Because the types above describe what the API promises, check the fields you depend on. pager.response.snapshot can be None, and so can pager.items when a page carries no data:

pager = client.get_sports_matching_markets()
snapshot = pager.response.snapshot
if snapshot is not None and snapshot.observed_at is not None:
    print(snapshot.observed_at)
for event in pager.items or []:
    print(event.event_id)

An error response whose body does not match ErrorResponse still raises the typed error for its status, with e.body.code and e.body.message reading None for whatever the body did not carry.

Error handling

The SDK raises typed exceptions for known HTTP error codes:

Error classStatus codeDescription
BadRequestError400Invalid query parameters
UnauthorizedError401Missing or invalid API key
PaymentRequiredError402Monthly allowance exhausted or subscription payment recovery required
ForbiddenError403Insufficient permissions
NotFoundError404Market or event not found
ConflictError409Bare identifier resolves on multiple providers; retry with provider or a composite ID
TooManyRequestsError429Rate limit exceeded
BadGatewayError502Upstream provider or API-key authorization dependency failure
ServiceUnavailableError503Service temporarily unavailable

The client retries a 408, 429 or 5xx response twice by default (max_retries, on the client or in request_options). A 429 waits for Retry-After, or else until the second-based X-RateLimit-Reset. Any other retried status honours Retry-After and otherwise backs off exponentially from one second. No wait exceeds 60 seconds. A 409 is never retried: send the request again with a provider from its candidates.

A request that gets no response because the connection was refused, reset or closed by the server is retried the same way, with the same backoff and the same limit. A timeout is never retried, and cancelling an async call ends a wait at once. Every call is a read, so repeating one is safe. The Go and TypeScript SDKs follow the same policy, and every response and error carries the same request id.

from predictorsdk import PredictorSDK, BadRequestError

client = PredictorSDK(token="YOUR_API_KEY")

try:
    # A venue ID needs its provider prefix: "kalshi:KXNBAGAME-26OCT20OKCSAS".
    client.lookup_sports_matching_markets(source_id="KXNBAGAME-26OCT20OKCSAS")
except BadRequestError as e:
    print(f"Bad request: {e.body.code}: {e.body.message}")

On this page