Metadata-Version: 2.4
Name: polymarketdata-sdk
Version: 0.1.0
Summary: Official Python SDK for the PolymarketData API.
Project-URL: Homepage, https://polymarketdata.co
Project-URL: Documentation, https://polymarketdata.co/docs
Author-email: PolymarketData <support@polymarketdata.co>
License: Apache-2.0
License-File: LICENSE
Requires-Python: >=3.10
Requires-Dist: httpx<1,>=0.27
Requires-Dist: pydantic<3,>=2.7
Provides-Extra: dataframe
Requires-Dist: pandas<3,>=2.1; extra == 'dataframe'
Description-Content-Type: text/markdown

# polymarketdata-sdk

Official Python SDK for the [PolymarketData API](https://polymarketdata.co) — historical data for Polymarket prediction markets.

[![PyPI version](https://img.shields.io/pypi/v/polymarketdata-sdk)](https://pypi.org/project/polymarketdata-sdk/)
[![Python versions](https://img.shields.io/pypi/pyversions/polymarketdata-sdk)](https://pypi.org/project/polymarketdata-sdk/)
[![CI](https://github.com/polymarketdata/polymarketdata-sdk/actions/workflows/ci.yml/badge.svg)](https://github.com/polymarketdata/polymarketdata-sdk/actions/workflows/ci.yml)

## Installation

```bash
pip install polymarketdata-sdk
```

With optional pandas DataFrame helpers:

```bash
pip install "polymarketdata-sdk[dataframe]"
```

## Quickstart

```python
from polymarketdata import PolymarketDataClient

# api_key can also be set via the POLYMARKETDATA_API_KEY environment variable
with PolymarketDataClient(api_key="YOUR_API_KEY") as client:
    health = client.utility.health()
    print(health.status)          # "ok"

    usage = client.utility.usage()
    print(usage.plan)             # e.g. "pro"
    print(usage.limits.requests_remaining)  # requests left in the current window
```

## Discovery

### List and search markets

```python
from polymarketdata import PolymarketDataClient

with PolymarketDataClient(api_key="YOUR_API_KEY") as client:
    # List the 10 most-recently-updated series
    series_page = client.discovery.list_series(limit=10)
    for s in series_page.data:
        print(s.id, s.slug, s.title)

    # Cursor-based next page
    next_cursor = series_page.metadata.next_cursor
    if next_cursor:
        next_page = client.discovery.list_series(limit=10, cursor=next_cursor)

    # Search within a series
    events_page = client.discovery.list_events(series_slug="us-elections-2024")
    for e in events_page.data:
        print(e.id, e.title, e.tags)

    # Markets for a specific event
    markets_page = client.discovery.list_markets(event_slug="us-presidential-election-2024")
    for m in markets_page.data:
        print(m.id, m.question)
        for token in (m.tokens or []):
            print(f"  token: {token.id} ({token.label})")

    # Fetch a single market by ID or slug
    market = client.discovery.get_market("will-trump-win-the-2024-presidential-election")
    print(market.market.question)
    print(market.market.status)   # "resolved", "open", etc.
    print(market.market.resolved_token_label)   # e.g. "Yes" if resolved

    # All available tags
    tags = client.discovery.list_tags()
    print(tags.data[:10])   # list[str]
```

### Auto-paginating iterators

The `iter_*` methods handle cursor pagination automatically, yielding one item at a time across as many pages as needed:

```python
from polymarketdata import PolymarketDataClient

with PolymarketDataClient(api_key="YOUR_API_KEY") as client:
    # Stream every series (or stop early with max_items)
    for series in client.discovery.iter_series(search="election", max_items=50):
        print(series.slug, series.title)

    # Nested discovery: series → events → markets
    for series in client.discovery.iter_series(tags=["politics"]):
        for event in client.discovery.iter_events(series_slug=series.slug):
            for market in client.discovery.iter_markets(event_slug=event.slug):
                print(f"{series.title} / {event.title} / {market.question}")
```

## History

Historical data requires a time range (`start_ts`, `end_ts`) and a `resolution`.
Timestamps can be passed as Unix ints, ISO-8601 strings, or `datetime` objects.

### Resolutions

| Enum | String | Interval |
|------|--------|----------|
| `Resolution.ONE_MINUTE` | `"1m"` | 1 minute |
| `Resolution.FIVE_MINUTES` | `"5m"` | 5 minutes |
| `Resolution.FIFTEEN_MINUTES` | `"15m"` | 15 minutes |
| `Resolution.ONE_HOUR` | `"1h"` | 1 hour |
| `Resolution.SIX_HOURS` | `"6h"` | 6 hours |
| `Resolution.ONE_DAY` | `"1d"` | 1 day |

### Market metrics (volume, liquidity, spread)

```python
import time
from polymarketdata import PolymarketDataClient, Resolution

with PolymarketDataClient(api_key="YOUR_API_KEY") as client:
    result = client.history.get_market_metrics(
        "will-trump-win-the-2024-presidential-election",
        start_ts="2024-01-01T00:00:00Z",
        end_ts="2024-11-05T00:00:00Z",
        resolution=Resolution.ONE_DAY,
    )
    print(f"market_id={result.market_id}, resolution={result.resolution}")
    for dp in result.data:
        print(f"  t={dp.t}  volume={dp.volume:.2f}  liquidity={dp.liquidity:.2f}  spread={dp.spread:.4f}")

    # Iterate all data points across pages without manual cursor management
    for dp in client.history.iter_market_metrics(
        "will-trump-win-the-2024-presidential-election",
        start_ts="2024-01-01T00:00:00Z",
        end_ts=int(time.time()),
        resolution=Resolution.ONE_DAY,
    ):
        print(dp.t, dp.volume)
```

### Token prices

Each Polymarket market has two tokens (e.g. "Yes" and "No"). The market-level endpoint returns prices for all tokens keyed by label; the token-level endpoint returns a single price series.

```python
from polymarketdata import PolymarketDataClient, Resolution

with PolymarketDataClient(api_key="YOUR_API_KEY") as client:
    # All tokens for a market
    result = client.history.get_market_prices(
        "will-trump-win-the-2024-presidential-election",
        start_ts="2024-10-01T00:00:00Z",
        end_ts="2024-11-05T00:00:00Z",
        resolution=Resolution.ONE_HOUR,
    )
    print(result.tokens)  # {"Yes": "<token_id>", "No": "<token_id>"}
    for label, points in result.data.items():
        print(f"{label}: {len(points)} data points")
        for dp in points[:3]:
            print(f"  t={dp.t}  p={dp.p:.4f}")

    # Single token — useful for streaming a specific outcome's price history
    yes_token_id = result.tokens["Yes"]
    single = client.history.get_token_prices(
        yes_token_id,
        start_ts="2024-10-01T00:00:00Z",
        end_ts="2024-11-05T00:00:00Z",
        resolution=Resolution.ONE_HOUR,
    )
    print(f"token_label={single.token_label}")
    for dp in single.data:
        print(f"  t={dp.t}  p={dp.p:.4f}")

    # Auto-paginating iterator for a single token
    for dp in client.history.iter_token_prices(
        yes_token_id,
        start_ts="2024-01-01T00:00:00Z",
        end_ts="2024-11-05T00:00:00Z",
        resolution=Resolution.ONE_DAY,
    ):
        print(dp.t, dp.p)
```

### Order book snapshots

Order books are returned as lists of `[price, size]` pairs.

```python
from polymarketdata import PolymarketDataClient, Resolution

with PolymarketDataClient(api_key="YOUR_API_KEY") as client:
    result = client.history.get_market_books(
        "will-trump-win-the-2024-presidential-election",
        start_ts="2024-10-01T00:00:00Z",
        end_ts="2024-10-02T00:00:00Z",
        resolution=Resolution.ONE_HOUR,
    )
    for label, snapshots in result.data.items():
        print(f"Token: {label}  ({len(snapshots)} snapshots)")
        for snap in snapshots[:2]:
            print(f"  t={snap.t}")
            print(f"  bids (top 3): {snap.bids[:3]}")   # [[price, size], ...]
            print(f"  asks (top 3): {snap.asks[:3]}")

    # Iterate all book snapshots for a single token
    yes_token_id = result.tokens["Yes"]
    for snap in client.history.iter_token_books(
        yes_token_id,
        start_ts="2024-10-01T00:00:00Z",
        end_ts="2024-11-05T00:00:00Z",
        resolution=Resolution.ONE_HOUR,
    ):
        best_bid = snap.bids[0][0] if snap.bids else None
        best_ask = snap.asks[0][0] if snap.asks else None
        print(f"t={snap.t}  bid={best_bid}  ask={best_ask}")
```

## DataFrame helpers

If you have pandas installed (`pip install "polymarketdata-sdk[dataframe]"`), you can convert responses directly to DataFrames. Timestamps are automatically parsed to timezone-aware `datetime64[ns, UTC]`:

```python
from polymarketdata import PolymarketDataClient, Resolution
from polymarketdata.dataframe import to_dataframe_metrics, to_dataframe_prices, to_dataframe_books

with PolymarketDataClient(api_key="YOUR_API_KEY") as client:
    metrics_resp = client.history.get_market_metrics(
        "will-trump-win-the-2024-presidential-election",
        start_ts="2024-01-01T00:00:00Z",
        end_ts="2024-11-05T00:00:00Z",
        resolution=Resolution.ONE_DAY,
    )
    df = to_dataframe_metrics(metrics_resp.data)
    print(df.dtypes)
    # t             datetime64[ns, UTC]
    # volume                    float64
    # liquidity                 float64
    # spread                    float64
    print(df.head())

    prices_resp = client.history.get_market_prices(
        "will-trump-win-the-2024-presidential-election",
        start_ts="2024-10-01T00:00:00Z",
        end_ts="2024-11-05T00:00:00Z",
        resolution=Resolution.ONE_HOUR,
    )
    # to_dataframe_prices accepts either the full response or a list of PriceDataPoint
    yes_df = to_dataframe_prices(prices_resp.data["Yes"])
    print(yes_df.head())
```

## Error handling

All SDK errors inherit from `PolymarketDataError`:

```python
from polymarketdata import (
    PolymarketDataClient,
    AuthenticationError,
    NotFoundError,
    RateLimitError,
    PolymarketDataError,
)

with PolymarketDataClient(api_key="YOUR_API_KEY") as client:
    try:
        market = client.discovery.get_market("nonexistent-market-slug")
    except NotFoundError as e:
        print(f"404 — not found: {e.detail}")
    except AuthenticationError:
        print("Check your API key")
    except RateLimitError as e:
        print(f"Rate limited — status {e.status_code}, request_id={e.request_id}")
    except PolymarketDataError as e:
        print(f"SDK error {e.status_code}: {e.detail}")
```

### Exception hierarchy

| Exception | HTTP status |
|-----------|-------------|
| `BadRequestError` | 400 |
| `AuthenticationError` | 401 |
| `PermissionDeniedError` | 403 |
| `NotFoundError` | 404 |
| `RateLimitError` | 429 |
| `ServerError` | 5xx |
| `NetworkError` | — (network failure) |
| `RequestTimeoutError` | — (timeout) |

## Retry configuration

The client retries on `429 Too Many Requests` and `5xx` errors with exponential back-off + jitter. Adjust via the constructor:

```python
from polymarketdata import PolymarketDataClient

client = PolymarketDataClient(
    api_key="YOUR_API_KEY",
    max_retries=4,           # default: 2
    retry_backoff_base=1.0,  # default: 0.5 s
    retry_backoff_max=30.0,  # default: 8.0 s
    timeout=60.0,            # default: 30.0 s
)
```

## Rate-limit awareness

```python
from polymarketdata import PolymarketDataClient

with PolymarketDataClient(api_key="YOUR_API_KEY") as client:
    usage = client.utility.usage()
    print(f"Plan: {usage.plan}")
    print(f"Requests/min allowed: {usage.limits.requests_per_minute}")
    print(f"Requests remaining:   {usage.limits.requests_remaining}")
    print(f"Max history days:     {usage.limits.max_history_days}")
    print(f"Resets at:            {usage.reset_at}  (Unix timestamp)")
```

## HTTP metadata

Every response carries `.meta` (HTTP metadata) and `.raw` (unmodified response body):

```python
from polymarketdata import PolymarketDataClient

with PolymarketDataClient(api_key="YOUR_API_KEY") as client:
    result = client.utility.health()
    if result.meta:
        print(result.meta.status_code)    # 200
        print(result.meta.request_id)     # "abc-123"
    print(result.raw)                      # RawPayload with all raw fields
```

## Development

```bash
# Install all dev deps
uv sync --group dev

# Run the full check suite
uv run ruff check .
uv run mypy src scripts
uv run pytest

# Run the live smoke test (requires POLYMARKETDATA_API_KEY)
uv run python scripts/smoke_test.py

# Build a distribution
uv build
```

## OpenAPI model generation

Pydantic models are auto-generated from the pinned OpenAPI snapshot at `openapi/openapi.json`.

```bash
# Regenerate after updating openapi.json
uv run python scripts/update_openapi.py

# Check for drift (also runs in CI)
uv run python scripts/update_openapi.py --check
```

## License

Apache 2.0 — see [LICENSE](LICENSE).
