prediction-markets-api
A unified Python client for prediction markets. Currently supports Polymarket, with Kalshi and Limitless adapters planned.
Features
- Unified API across multiple prediction market venues
- Type-safe data models with Pydantic validation
- Normalized data: Prices in 0..1 probability space, UTC timestamps, explicit currencies
- Safe trading:
dry_runmode ON by default - Async-first design with
httpx - Well-tested with recorded API fixtures
Installation
pip install prediction-markets-api
For development:
pip install prediction-markets-api[dev]
Quick Start
Reading Market Data
import asyncio
from prediction_markets_api import PolymarketClient
async def main():
async with PolymarketClient() as client:
# List active markets
async for market in client.list_markets(closed=False, limit=5):
print(f"\n{market.question}")
print(f" Volume: ${market.volume:,.2f}")
for outcome in market.outcomes:
print(f" - {outcome.name}: {outcome.price:.2%}")
# Get order book for first outcome
if market.outcomes:
outcome = market.outcomes[0]
book = await client.get_order_book(outcome.id)
if book.bids and book.asks:
print(f" Spread: {book.bids[0].price:.4f} / {book.asks[0].price:.4f}")
asyncio.run(main())
Getting Prices
import asyncio
from prediction_markets_api import PolymarketClient
from prediction_markets_api.models.base import OrderSide
async def main():
async with PolymarketClient() as client:
# Get first market with outcomes
async for market in client.list_markets(closed=False, limit=10):
if market.outcomes:
outcome = market.outcomes[0]
break
# Get best executable prices
buy_price = await client.get_price(outcome_id=outcome.id, side=OrderSide.BUY)
sell_price = await client.get_price(outcome_id=outcome.id, side=OrderSide.SELL)
print(f"Best ask (to buy): {buy_price.price:.4f}")
print(f"Best bid (to sell): {sell_price.price:.4f}")
asyncio.run(main())
Getting Historical Trades
import asyncio
from prediction_markets_api import PolymarketClient
async def main():
async with PolymarketClient() as client:
# Get first market with outcomes
async for market in client.list_markets(closed=False, limit=10):
if market.outcomes:
outcome = market.outcomes[0]
break
# Get historical trades
async for trade in client.get_trades(outcome_id=outcome.id, limit=10):
print(f"{trade.timestamp}: {trade.side.value} {trade.size} @ {trade.price}")
asyncio.run(main())
How It Works
Data Model Contract
All venues return data through a unified model with strict guarantees:
Price Normalization:
- Prices are normalized to 0..1 probability space (e.g., 0.65 = 65% probability)
- Original venue prices preserved in
raw_pricefield for reference OrderSide.BUYmeans buying the outcome (get_price returns best ask)OrderSide.SELLmeans selling the outcome (get_price returns best bid)
Timestamps:
- All timestamps are timezone-aware UTC (never naive datetimes)
- Created dates, end dates, and trade timestamps use
datetimewithtzinfo=timezone.utc
Currency:
- All amounts include explicit currency (
Currency.USD,Currency.USDC) - No ambiguous currency-free numbers
Partial Responses:
- Markets with incomplete data are flagged with
partial=True - Only raise errors for invalid data, not incomplete data
- Allows graceful degradation when APIs return partial information
Order Book Semantics:
- Bids sorted descending (best bid = highest price first)
- Asks sorted ascending (best ask = lowest price first)
- Books validated to never be crossed (best_bid < best_ask)
- Sizes:
best_bid_size,best_ask_size,open_interest,liquidity
Market Status:
- Each market includes
venue_status(raw venue string) andstatus(normalized enum) - Normalized statuses:
MarketStatus.OPEN,MarketStatus.CLOSED,MarketStatus.RESOLVED resolved=Truemeans the outcome is known (may still be settling)winning_outcome: str | Nonecontains the name of the winning outcome when resolved (e.g., "Yes", "No")- Unknown venue statuses log a warning and return
status=None(not silently mapped)
Status Mapping Table:
| Venue | Raw Status | Normalized Status | Resolved | Notes |
|---|---|---|---|---|
| Kalshi | active |
OPEN |
False | Market is open for trading (filter: open) |
initialized |
CLOSED |
False | Not yet opened, excluded by closed=None (filter: unopened) |
|
determined |
RESOLVED |
True | Outcome known, settling (filter: closed) |
|
finalized |
RESOLVED |
True | Fully settled and paid out (filter: settled) |
|
| Polymarket | open |
OPEN |
False | Market is open for trading (closed=false) |
closed |
CLOSED |
False | Market closed but outcome not yet determined | |
resolved |
RESOLVED |
True | Market resolved, outcome prices settled to 0/1 | |
| Limitless | FUNDED |
OPEN |
False | Market is active and funded |
Notes:
- Kalshi filter parameter names differ from response status values. The table shows response values with filter parameters in parentheses.
- Kalshi resolved markets with
result='scalar'(e.g., cancelled events): These are marked asresolved=Truewithstatus=RESOLVEDbutwinning_outcome=Nonesince no yes/no outcome won. This is acceptable and documented. - Polymarket resolution detection:
- No explicit resolution status fields in API response (umaResolutionStatuses always empty, resolvedBy/resolutionSource not status indicators)
- Resolution determined by strict price settlement check: >0.999 and <0.001 thresholds
- This minimizes false positives from markets trading near certainty but not yet formally resolved
- Winning outcome automatically detected from settled prices
- Limitless only exposes
FUNDEDmarkets via/markets/activeendpoint. Callinglist_markets(closed=True)raisesNotImplementedErrorwith a clear explanation, since the API does not provide an endpoint for closed/resolved markets.
Adapter Architecture
Each venue implements the BaseClient interface:
import asyncio
from prediction_markets_api import PolymarketClient, KalshiClient
async def main():
# All clients provide the same methods:
async with PolymarketClient() as client:
async for market in client.list_markets(limit=10): # Standard method
print(market.question)
asyncio.run(main())
Unified Methods (Read-Only for 0.9):
list_markets(closed, limit, page_size, *, venue_params)- List markets with paginationiter_markets(closed)- Iterate all markets (no limit)get_market(market_id)- Get single market by IDget_order_book(outcome_id)- Get sorted, validated order bookget_price(outcome_id, side)- Get best executable priceget_trades(outcome_id, limit, page_size)- Historical trades iterator (up tolimittotal, default 100)iter_trades(outcome_id)- Iterate all historical trades (unbounded)
Note: Trading functionality with
place_order()is planned for version 1.0. Version 0.9 is read-only.
Venue-Specific Parameters:
- Venue-specific filters are keyword-only arguments (no
**kwargs) - Example:
client.list_markets(category="politics")for Kalshi - Prevents accidental parameter mismatches between venues
API Keys and Configuration
Version 0.9 (Read-Only): No authentication required. All market data, prices, order books, and trades are accessible without API keys.
Trading (Planned for 1.0): Will require API keys via environment variables.
Geographic Restrictions
Be aware of venue restrictions:
- Polymarket: US users blocked
- Kalshi: US only (CFTC regulated)
- Limitless: No restrictions (planned)
Exception Hierarchy
The library provides a consistent exception hierarchy:
from prediction_markets_api import (
PredictionMarketsError, # Base exception
NotFoundError, # Market/outcome/token not found
NoLiquidityError, # Empty order book for existing market
VenueAPIError, # HTTP errors, invalid responses
RateLimitError, # HTTP 429 rate limit
)
Exception Types:
PredictionMarketsError: Base exception for all library errorsNotFoundError: Resource not found (markets, outcomes, tokens)- Attributes:
resource_type,resource_id - Raised on HTTP 404 or empty results
- Attributes:
NoLiquidityError: No liquidity available (empty order book for an existing market)- Attributes:
market_id,side(BUY/SELL) - Raised by
get_price()when the order book has no bids/asks on the requested side - Different from
NotFoundError(market doesn't exist) vs. market exists but has no liquidity
- Attributes:
VenueAPIError: HTTP errors, timeouts, invalid JSON responses- Attributes:
status_code,venue,url - Wraps all
httpxerrors for consistent handling
- Attributes:
RateLimitError: Rate limit exceeded (HTTP 429)- Subclass of
VenueAPIError - Attributes:
retry_after(seconds, from Retry-After header if available)
- Subclass of
User Input Errors:
ValueError: Invalid parameters (e.g., price not in 0..1, negative size)NotImplementedError: Unsupported operations (e.g.,LimitlessClient.list_markets(closed=True))
Example:
import asyncio
from prediction_markets_api import PolymarketClient, NotFoundError, VenueAPIError
async def main():
async with PolymarketClient() as client:
try:
market = await client.get_market("nonexistent")
except NotFoundError as e:
print(f"Market not found: {e.resource_id}")
except VenueAPIError as e:
print(f"API error: {e.status_code} from {e.venue}")
asyncio.run(main())
Data Model
All venues return data through a unified model:
Core Types
- Market: Question, outcomes, volume, liquidity, timestamps
- Outcome: Individual tradeable token (e.g., "Yes"/"No")
- OrderBook: Sorted bids/asks, never crossed
- Price: Best executable price for a side
- Trade: Historical trade record
Key Guarantees
- Prices normalized to 0..1 (probability space)
- Original venue prices preserved in
raw_pricefield
- Original venue prices preserved in
- All timestamps are timezone-aware UTC
- Never naive datetimes
- Amounts include explicit currency (
USD,USDC) - Partial responses allowed (flagged, not an error)
- Only raise on invalid data
- Order books are sorted and validated
- Bids descending, asks ascending
- Never crossed (best_bid < best_ask)
Example: Market Object
from datetime import datetime, timezone
from decimal import Decimal
from prediction_markets_api.models.base import Market, Outcome, Currency
market_id = "0x1234..."
market = Market(
id=market_id,
venue="polymarket",
question="Will Bitcoin reach $100k by Dec 31?",
outcomes=[
Outcome(id="token_yes", market_id=market_id, name="Yes", price=Decimal("0.65")),
Outcome(id="token_no", market_id=market_id, name="No", price=Decimal("0.35")),
],
created_at=datetime(2026, 1, 15, tzinfo=timezone.utc),
end_date=datetime(2026, 12, 31, tzinfo=timezone.utc),
volume=Decimal("1250000.50"),
currency=Currency.USDC,
partial=False,
)
print(market.question)
Examples
The examples/ directory contains runnable examples:
Compare Same Event Across Venues
python examples/compare_venues.py
Search for the same event (e.g., Bitcoin predictions) across Polymarket and Kalshi, comparing prices and volumes.
Price History Analysis
python examples/price_history.py
Fetch historical trades for an outcome and calculate price statistics, movements, and spreads.
Export to Pandas
pip install prediction-markets-api[pandas]
python examples/export_to_pandas.py
Export market data and trades to pandas DataFrames for statistical analysis, visualization, or CSV export.
See examples/README.md for details.
Trading Configuration
API Keys (Environment Variables Only)
# Polymarket
export POLYMARKET_API_KEY="your_key"
export POLYMARKET_API_SECRET="your_secret"
export POLYMARKET_WALLET_ADDRESS="0x..."
Never hardcode API keys in your code.
Safety Features
dry_run=Trueby default: Orders are validated and signed but not sentmax_order_sizelimit: Configurable per-client safety limit- Explicit opt-in for real trading: Must pass
dry_run=False
Venues & referral links
This library is free and open source (MIT). Some links below are referral links: if you sign up through them, the venue may pay the project a share of fees or a bonus. It costs you nothing extra, and using the library never requires them. Plain links are given next to each one.
| Venue | Sign up | Referral? | Closed markets | Notes |
|---|---|---|---|---|
| Limitless | limitless.exchange | yes (r=0D2RQUXIT4) |
no | plain link: https://limitless.exchange |
| Polymarket | polymarket.com | no | yes | |
| Kalshi | kalshi.com | no | yes |
Referral links are defined in src/prediction_markets_api/venues.py (the same registry that generates this table), so you can see exactly what is linked and change it in a fork.
Trading and builder-code attribution are not part of 0.9 (read-only). How builder codes will work, what they cost and how to turn them off will be documented here before 1.0.
Supported Venues
Sign-up links and referral disclosure: see Venues & referral links.
| Venue | Chain | Status | Read | Trade | API Key (Read) | API Key (Trade) | Geo Restrictions | Link |
|---|---|---|---|---|---|---|---|---|
| Polymarket | Polygon | ✅ Implemented | ✅ | — | No | Yes | US blocked | Link |
| Kalshi | None | ✅ Implemented | ✅ | — | No | Yes | US only | Link |
| Limitless | Base | ✅ Implemented | ✅ | — | No | Yes | None | Link |
| Hyperliquid Outcomes (HIP-4) | Hyperliquid | 🚧 Planned | — | — | No | Yes | None | Link |
| Predict.fun | Base | 🚧 Planned | — | — | No | Yes | None | Link |
| Opinion | Unknown | 🚧 Planned | — | — | No | Yes | None | Link |
| Pascal | Base | 🚧 Planned | — | — | No | Yes | None | Link |
| SX Bet | Arbitrum | 🚧 Planned | — | — | No | Yes | None | Link |
| Azuro/Bookmaker.xyz | Polygon | 🚧 Planned | — | — | No | Yes | None | Link |
| Jupiter Prediction | Solana | 🚧 Planned | — | — | No | Yes | None | Link |
| Polymarket US | Unknown | 🚧 Planned | — | — | No | Yes | US only | Link |
| Novig | None | 🚧 Planned | — | — | No | Yes | None | Link |
| World | Unknown | 🚧 Planned | — | — | No | Yes | None | Link |
| BetDEX | Solana | 🚧 Planned | — | — | No | Yes | None | Link |
Development
Setup
git clone https://github.com/prediction-markets-api/prediction-markets-api.git
cd prediction-markets-api
python -m venv venv
source venv/bin/activate # or `venv\Scripts\activate` on Windows
pip install -e ".[dev]"
Running Tests
# Run all tests (uses recorded fixtures, no network)
pytest
# Run with coverage
pytest --cov=src/prediction_markets_api --cov-report=html
# Run only live tests (requires network, opt-in)
pytest -m live
# Skip live tests (default)
pytest -m "not live"
Linting and Type Checking
# Format code
ruff format .
# Lint
ruff check .
# Type check
mypy src/prediction_markets_api
CI
GitHub Actions runs:
- Lint (ruff)
- Type check (mypy)
- Tests on Python 3.10, 3.11, 3.12 (Linux, macOS, Windows)
All tests use recorded API fixtures (no live network calls in CI).
Architecture
prediction-markets-api/
├── src/prediction_markets_api/
│ ├── models/
│ │ └── base.py # Unified data models
│ ├── adapters/
│ │ ├── base.py # Common client interface
│ │ └── polymarket.py # Polymarket adapter
│ └── __init__.py
├── tests/
│ ├── fixtures/ # Recorded API responses
│ ├── test_models.py # Model validation tests
│ ├── test_polymarket.py # Adapter tests (mocked)
│ └── test_polymarket_live.py # Live smoke tests (opt-in)
└── pyproject.toml
Contributing
Contributions welcome! Please:
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Add tests for new functionality
- Ensure CI passes (
pytest,ruff,mypy) - Submit a Pull Request
License
MIT License - see LICENSE for details.
Roadmap
- Complete Polymarket trading implementation
- Add Kalshi adapter
- Add Limitless adapter
- WebSocket support for real-time data
- Portfolio management utilities
- Order history and fills tracking
Support
- Issues: GitHub Issues
- Discussions: GitHub Discussions
Disclaimer: This is an independent open-source project and is not affiliated with or endorsed by any prediction market platform.
Metadata
Release files for prediction-markets-api 0.9.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| prediction_markets_api-0.9.0.tar.gz | 33.8 kB | With provenance | Details for prediction_markets_api-0.9.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| prediction_markets_api-0.9.0-py3-none-any.whl | Python 3 | none | any | Details for prediction_markets_api-0.9.0-py3-none-any.whl |
Total release size: 72.1 kB
Release files / prediction_markets_api-0.9.0.tar.gz
| Download URL | prediction_markets_api-0.9.0.tar.gz |
|---|---|
| Size | 33.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
8d6b6eee9c247dddb6606f1d730b8e72d78297480ea0be87993b4dd6cbf0c442
|
|
BLAKE2b-256 checksum How to use checksums |
2d5e56d36852e2f7d97b2771f96a88fc4d1677f5f16f9bd45a129244f1c39173
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 23, 2026.
Transparency logRelease files / prediction_markets_api-0.9.0-py3-none-any.whl
| Download URL | prediction_markets_api-0.9.0-py3-none-any.whl |
|---|---|
| Size | 38.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
4a47deefe018ee6cee4c5184fa042eee124a5ba5434972d0688b082cfd9d8e55
|
|
BLAKE2b-256 checksum How to use checksums |
c7820910b4ffaf2530f5b28a621e3b54f7003fd5d781e4db5e14fa56ab85a041
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 23, 2026.
Transparency logSecurity for prediction-markets-api 0.9.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for prediction_markets_api-0.9.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 23, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.