# Rulebook Python SDK

The official Python client for the [Rulebook Company API](https://api.rulebookcompany.com) — access exchange fee schedule and rebate data across US trading venues.

[![Python 3.11+](https://img.shields.io/badge/python-3.11%2B-blue)](https://www.python.org/downloads/)
[![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](LICENSE)

## Installation

```bash
pip install rulebook-sdk
```

## Usage

```python
from rulebook import Rulebook

client = Rulebook(api_key="your-api-key")

# List all exchanges
exchanges = client.exchanges.list()
for exchange in exchanges:
    print(f"{exchange.name}: {exchange.display_name} ({exchange.record_count} records)")

# Get details for a specific exchange
detail = client.exchanges.retrieve("NYSE")
print(f"Date range: {detail.date_range.earliest} to {detail.date_range.latest}")
print(f"Fee types: {detail.fee_types}")
print(f"Actions: {detail.actions}")

# List fee schedule results (with filtering)
results = client.fee_schedule_results.list(
    exchange_name=["fee_cboe_us_options"],
    fee_type=["Option"],
    latest_only=True,
    page_size=10,
)
for item in results.data:
    print(f"{item.exchange_name} | {item.fee_type} | {item.fee_amount}")

# Get a single fee schedule result
result = client.fee_schedule_results.retrieve("some-uuid")

# Get available filter values
filters = client.fee_schedule_results.get_filters()
print(f"Exchanges: {filters.exchange_names}")
print(f"Fee types: {filters.fee_types}")

# Get results by extraction version
version_results = client.fee_schedule_results.get_results_by_version("version-uuid")

client.close()
```

## Authentication

Pass your API key directly or set the `RULEBOOK_API_KEY` environment variable:

```python
# Option 1: Pass directly
client = Rulebook(api_key="...")

# Option 2: Environment variable
# export RULEBOOK_API_KEY=...
client = Rulebook()
```

## Async support

Every method has an async counterpart via `AsyncRulebook`:

```python
import asyncio
from rulebook import AsyncRulebook

async def main():
    async with AsyncRulebook(api_key="your-api-key") as client:
        exchanges = await client.exchanges.list()
        detail = await client.exchanges.retrieve("NYSE")

asyncio.run(main())
```

## Error handling

The SDK raises typed exceptions for all API errors:

```python
from rulebook import Rulebook, NotFoundError, AuthenticationError, RateLimitError

client = Rulebook()

try:
    detail = client.exchanges.retrieve("UNKNOWN")
except NotFoundError:
    print("Exchange not found")
except AuthenticationError:
    print("Invalid API key")
except RateLimitError as e:
    print(f"Rate limited — retry after: {e.response.headers.get('retry-after')}")
```

**Exception hierarchy:**

| Exception | Status Code | Description |
|---|---|---|
| `BadRequestError` | 400 | Invalid request parameters |
| `AuthenticationError` | 401 | Missing or invalid API key |
| `PermissionDeniedError` | 403 | Insufficient access for this resource |
| `NotFoundError` | 404 | Resource does not exist |
| `UnprocessableEntityError` | 422 | Request validation failed |
| `RateLimitError` | 429 | Too many requests |
| `InternalServerError` | 5xx | Server-side error |
| `APIConnectionError` | — | Network failure |
| `APITimeoutError` | — | Request timed out |

## Raw response access

Access HTTP status codes, headers, and the raw response via `with_raw_response`:

```python
raw = client.exchanges.with_raw_response.retrieve("NYSE")
print(raw.status_code)                      # 200
print(raw.headers.get("content-type"))      # application/json

detail = raw.parse()                        # ExchangeDetail
```

## Client configuration

```python
client = Rulebook(
    api_key="...",
    base_url="https://api.rulebookcompany.com/api/v1",  # default
    timeout=30.0,         # seconds (default: 30)
    max_retries=2,        # retry transient failures (default: 2)
    default_headers={},   # headers sent on every request
)
```

### Per-request overrides

Override timeout or add headers for a single request without modifying the client:

```python
exchanges = client.exchanges.list(
    extra_headers={"X-Request-Id": "abc-123"},
    timeout=60.0,
)
```

### Immutable client copies

Create a new client with different settings using `with_options()`:

```python
slow_client = client.with_options(timeout=120.0)
```

## Retries

The SDK automatically retries transient errors (408, 409, 429, 500, 502, 503, 504) with exponential backoff and jitter. Configure with `max_retries`:

```python
client = Rulebook(max_retries=5)  # default is 2
```

## Response types

All responses are strongly typed with Pydantic models:

```python
from rulebook.types import (
    Exchange,
    ExchangeDetail,
    DateRange,
    FeeScheduleResult,
    FeeScheduleResultFilters,
    PaginatedResponse,
)
```

**`Exchange`** — returned by `list()`

| Field | Type | Description |
|---|---|---|
| `name` | `str` | Exchange identifier (e.g., `"NYSE"`) |
| `display_name` | `str` | Full name (e.g., `"New York Stock Exchange"`) |
| `fee_types` | `List[str]` | Available fee types (e.g., `["Equity", "Option"]`) |
| `record_count` | `int` | Total fee schedule records |

**`ExchangeDetail`** — returned by `retrieve()`

| Field | Type | Description |
|---|---|---|
| `name` | `str` | Exchange identifier |
| `display_name` | `str` | Full name |
| `date_range` | `DateRange` | Earliest and latest dates of available data |
| `fee_types` | `List[str]` | Available fee types |
| `fee_categories` | `List[str]` | Fee categories (e.g., `["Fee And Rebates"]`) |
| `actions` | `List[str]` | Trade actions (e.g., `["Make", "Take", "Open"]`) |
| `participants` | `List[str]` | Participant types (e.g., `["Market Maker", "Customer"]`) |
| `symbol_classifications` | `List[str]` | Symbol classifications (e.g., `["ETF", "Equity"]`) |
| `symbol_types` | `List[str]` | Symbol types (e.g., `["Penny", "Non Penny"]`) |
| `trade_types` | `List[str]` | Trade types (e.g., `["Simple Order"]`) |
| `record_count` | `int` | Total fee schedule records |

## Maintainers

- [Muhammad Haris Azaz Khan](https://github.com/haris-khan1596)
- [Abdul Muizz Anwar](https://github.com/abdul-muizz1310)

## Requirements

- Python 3.11+
- Dependencies: `httpx`, `pydantic`, `anyio`

## License

MIT — see [LICENSE](LICENSE) for details.
