Metadata-Version: 2.4
Name: owls-insight
Version: 0.2.0
Summary: Official Python SDK for the Owls Insight real-time sports betting odds API
Project-URL: Homepage, https://owlsinsight.com
Project-URL: Documentation, https://owlsinsight.com/docs
Project-URL: Repository, https://github.com/Davidgsilva/owls-insight-sdk-python
Author-email: Owls Insight <david@wisesportsai.com>
License: MIT
License-File: LICENSE
Keywords: api,betting,draftkings,fanduel,mlb,nba,nfl,nhl,odds,pinnacle,real-time,soccer,sports,tennis,websocket
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Typing :: Typed
Requires-Python: >=3.9
Requires-Dist: aiohttp>=3.9
Requires-Dist: httpx>=0.27
Requires-Dist: pydantic>=2.5
Requires-Dist: python-socketio>=5.11
Requires-Dist: websocket-client>=1.7
Provides-Extra: dev
Requires-Dist: mypy>=1.8; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
Requires-Dist: pytest>=8; extra == 'dev'
Requires-Dist: ruff>=0.4; extra == 'dev'
Description-Content-Type: text/markdown

# Owls Insight Python SDK

Official Python SDK for the [Owls Insight](https://owlsinsight.com) real-time sports betting odds API. Sync **and** async clients, fully typed with Pydantic v2, plus a Socket.io WebSocket client for live streams.

```bash
pip install owls-insight
```

Requires Python 3.9+. Get an API key at [owlsinsight.com](https://owlsinsight.com).

## Quick start (sync)

```python
from owls_insight import OwlsInsight

client = OwlsInsight(api_key="owlsinsight_...")

# REST: current NBA odds. `data` is keyed by sportsbook -> list of events.
odds = client.rest.get_odds("nba", books=["pinnacle", "fanduel"])
for book, events in odds.data.items():
    for event in events:
        print(book, event.home_team, "vs", event.away_team)

# WebSocket: stream live updates
client.ws.connect()
client.ws.subscribe(sports=["nba"], books=["pinnacle"])
client.ws.on("odds-update", lambda data: print("live:", data))

# or block for the next event
update = client.ws.wait_for("odds-update", timeout=15)

client.destroy()
```

## Quick start (async)

```python
import asyncio
from owls_insight import AsyncOwlsInsight

async def main():
    async with AsyncOwlsInsight(api_key="owlsinsight_...") as client:
        odds = await client.rest.get_odds("nba", books=["pinnacle"])
        print(sum(len(events) for events in odds.data.values()), "games")

        await client.ws.connect()
        await client.ws.subscribe(sports=["nba"], books=["pinnacle"])
        update = await client.ws.wait_for("pinnacle-realtime", timeout=30)
        print("realtime:", update)

asyncio.run(main())
```

## Authentication

Pass your API key to the constructor. REST uses the `Authorization: Bearer` header; the WebSocket uses the `?apiKey=` query string. Both are handled for you.

```python
import os
client = OwlsInsight(api_key=os.environ["OWLS_INSIGHT_API_KEY"])
```

## REST

Method names are `snake_case` (the JS SDK's `getOdds` is `get_odds` here). Every method returns a typed Pydantic model; unknown fields from the evolving API are preserved (`model_extra`), never dropped.

Full parity with the TypeScript SDK — every endpoint is available on both `client.rest` (sync) and the async client.

| Area | Methods |
|------|---------|
| Odds | `get_odds`, `get_moneyline`, `get_spreads`, `get_totals`, `get_realtime`, `get_ps3838_realtime`, `get_esports_realtime`, `get_ev`, `list_events`, `get_one_x_bet_soccer`, `get_prophetx_odds` |
| Props | `get_props`, `get_book_props`, `get_props_history`, `get_props_stats`, `get_book_props_stats` |
| Scores / schedule | `get_scores`, `get_schedule`, `get_results`, `get_splits`, `normalize`, `normalize_batch` |
| Stats | `get_stats`, `get_match_stats`, `get_h2h`, `get_player_averages` |
| Line history | `get_odds_history`, `get_moneyline_history`, `get_spread_history`, `get_totals_history` |
| Historical | `get_history_games`, `get_history_odds`, `get_history_props`, `get_history_stats`, `get_history_tennis_stats`, `get_game_stats_detail`, `get_closing_odds`, `get_historical_player_props`, `get_public_betting`, `get_cs2_matches`, `get_cs2_match`, `get_cs2_players` |
| v2 Source API | `get_stake_v2`, `get_hard_rock_events`/`_leagues`/`get_hard_rock_ladder`, `get_bet365_v2`/`_leagues`, `get_draftkings_v2`/`_leagues`, `get_fanduel_v2`/`_leagues`, `get_mybookie_v2`, `get_thunderpick_v2`, `get_underdog_v2`, `get_bookmaker_v2`/`_leagues`, `get_kalshi_v2`/`_leagues`, `get_polymarket_v2`/`_leagues`, `get_pinnacle_v2`/`_leagues` |

> Methods whose response shape isn't individually modelled yet return a permissive `ApiResponse` envelope (`success`/`data`/`meta`, all fields preserved). Deep per-endpoint Pydantic typing is the remaining follow-on; the method surface is complete.

### Hard Rock odds decoding

Hard Rock v2 selections carry a `rootIdx`, not inline odds. Decode it client-side:

```python
from owls_insight import root_idx_to_american_odds
root_idx_to_american_odds(42)  # -325
root_idx_to_american_odds(72)  #  100  (even)
```

## WebSocket events

`odds-update`, `props-update` (and per-book `{book}-props-update`), `pinnacle-realtime`, `ps3838-realtime`, `esports-update`, `prophetx-update`, and the v2 `{book}-v2-update` deltas.

```python
client.ws.connect()
client.ws.subscribe(sports=["nba", "nhl"], books=["pinnacle", "fanduel"])
client.ws.subscribe_props(book="fanduel")           # props stream
client.ws.update_subscription(prophetx=True)        # merge, don't replace
client.ws.on("props-update", handle_props)
```

## Errors

```python
from owls_insight import AuthenticationError, RateLimitError, OwlsInsightError

try:
    client.rest.get_odds("nba")
except AuthenticationError:
    ...  # 401
except RateLimitError as e:
    print("retry after", e.retry_after_ms, "ms")
except OwlsInsightError as e:
    print(e.status, e.message)
```

## License

MIT
