Metadata-Version: 2.3
Name: whoop-client
Version: 0.1.0
Summary: A Python wrapper for the WHOOP API
Author: gabrielmbmb
Author-email: gabrielmbmb <gmartinbdev@gmail.com>
Requires-Dist: httpx>=0.28.0
Requires-Dist: pydantic>=2.10.0
Requires-Dist: mcp>=1.2.0 ; extra == 'mcp'
Requires-Python: >=3.10
Provides-Extra: mcp
Description-Content-Type: text/markdown

# whoop-client

A Python wrapper for the [WHOOP API](https://developer.whoop.com/).

Provides both synchronous and asynchronous clients with Pydantic models, automatic retries, OAuth2 token management, and an MCP server for AI assistants.

## Installation

```bash
pip install whoop-client
```

Requires Python 3.10+.

## Quick Start

### With an access token

```python
from whoop_client import WhoopClient

with WhoopClient("your-access-token") as client:
    profile = client.get_profile()
    print(f"Hello, {profile.first_name}!")

    for cycle in client.iter_cycles():
        if cycle.score:
            print(f"Strain: {cycle.score.strain}")
```

### Async

```python
import asyncio
from whoop_client import AsyncWhoopClient

async def main():
    async with AsyncWhoopClient("your-access-token") as client:
        profile = await client.get_profile()
        print(f"Hello, {profile.first_name}!")

        async for cycle in client.iter_cycles():
            if cycle.score:
                print(f"Strain: {cycle.score.strain}")

asyncio.run(main())
```

### With OAuth2 token refresh

```python
from whoop_client import WhoopClient, OAuthTokenProvider

token_provider = OAuthTokenProvider(
    client_id="your-client-id",
    client_secret="your-client-secret",
    refresh_token="your-refresh-token",
)

with WhoopClient(token_provider) as client:
    profile = client.get_profile()
```

The `OAuthTokenProvider` automatically refreshes expired tokens. An `AsyncOAuthTokenProvider` is available for the async client.

## API

### Clients

Both `WhoopClient` and `AsyncWhoopClient` accept the same constructor arguments:

```python
WhoopClient(
    token_provider,           # Access token string or TokenProvider instance
    *,
    base_url="...",           # Override API base URL
    timeout=30.0,             # Request timeout in seconds
    retry_policy=None,        # Custom RetryPolicy, or None for defaults
    httpx_client=None,        # Bring your own httpx.Client
)
```

### Endpoints

| Method | Description |
|--------|-------------|
| `get_cycles(...)` | List cycles (paginated) |
| `get_cycle_by_id(id)` | Get a single cycle |
| `get_cycle_sleep(id)` | Get sleep for a cycle |
| `get_cycle_recovery(id)` | Get recovery for a cycle |
| `get_recoveries(...)` | List recoveries (paginated) |
| `get_sleeps(...)` | List sleeps (paginated) |
| `get_sleep_by_id(id)` | Get a single sleep |
| `get_workouts(...)` | List workouts (paginated) |
| `get_workout_by_id(id)` | Get a single workout |
| `get_profile()` | Get basic user profile |
| `get_body_measurement()` | Get body measurements |
| `revoke_access()` | Revoke the OAuth access token |
| `get_activity_mapping(v1_id)` | Map a V1 activity ID to V2 |

Paginated endpoints accept `limit`, `start`, `end`, and `next_token` parameters. They return a `PaginatedResponse[T]` with `records` and `next_token` fields.

### Auto-Pagination

Use the `iter_*` methods to automatically paginate through all results:

- `iter_cycles(...)`
- `iter_recoveries(...)`
- `iter_sleeps(...)`
- `iter_workouts(...)`

These return `Iterator[T]` (sync) or `AsyncIterator[T]` (async).

### Retry Policy

Requests are retried automatically on transient failures (429, 5xx, timeouts, connection errors) with exponential backoff and jitter. Customize with `RetryPolicy`:

```python
from whoop_client import WhoopClient, RetryPolicy

policy = RetryPolicy(
    max_retries=5,
    backoff_factor=1.0,
    max_backoff_seconds=120.0,
)

client = WhoopClient("token", retry_policy=policy)
```

### Exceptions

All exceptions inherit from `WhoopError`:

| Exception | Trigger |
|-----------|---------|
| `WhoopBadRequestError` | 400 response |
| `WhoopAuthenticationError` | 401 response |
| `WhoopNotFoundError` | 404 response |
| `WhoopRateLimitError` | 429 response (has `retry_after` attribute) |
| `WhoopServerError` | 5xx response |
| `WhoopConnectionError` | Network failure |
| `WhoopTimeoutError` | Request timeout |
| `WhoopAuthTokenError` | Token refresh failure |

API errors carry `status_code` and `response` attributes for full introspection.

## Obtaining Tokens (`whoop-client auth`)

The `whoop-client auth` command runs the OAuth2 Authorization Code flow to obtain access and refresh tokens from WHOOP.

### Prerequisites

Set your OAuth2 credentials as environment variables (or pass them as flags):

```bash
export WHOOP_CLIENT_ID="your-client-id"
export WHOOP_CLIENT_SECRET="your-client-secret"
```

### Usage

```bash
whoop-client auth
```

This opens your browser, waits for the OAuth2 callback, and saves the tokens to `~/.config/whoop-client/tokens.json`.

### Options

| Flag | Description | Default |
|------|-------------|---------|
| `--client-id` | OAuth2 client ID | `$WHOOP_CLIENT_ID` |
| `--client-secret` | OAuth2 client secret | `$WHOOP_CLIENT_SECRET` |
| `--scopes` | OAuth2 scopes to request | All read scopes |
| `--port` | Local callback server port | `8080` |
| `--redirect-uri` | OAuth2 redirect URI | `http://localhost:8080/callback` |
| `--token-file` | Output file path | `~/.config/whoop-client/tokens.json` |
| `--no-open` | Print the URL instead of opening the browser | — |

### Example

```bash
# Use env vars, save to custom file
whoop-client auth --token-file my-tokens.json

# Pass credentials directly
whoop-client auth --client-id ID --client-secret SECRET

# Headless (e.g. SSH session) — prints the URL to visit manually
whoop-client auth --no-open
```

## MCP Server

`whoop-client` includes an [MCP](https://modelcontextprotocol.io/) server that exposes WHOOP data as tools for AI assistants like Claude.

### Installation

```bash
pip install "whoop-client[mcp]"
```

### Configuration

The server requires WHOOP API credentials via environment variables:

**Option 1: OAuth2 with auto-refresh (recommended)**

| Variable | Description |
|----------|-------------|
| `WHOOP_CLIENT_ID` | OAuth2 client ID |
| `WHOOP_CLIENT_SECRET` | OAuth2 client secret |
| `WHOOP_REFRESH_TOKEN` | OAuth2 refresh token |
| `WHOOP_ACCESS_TOKEN` | (Optional) Initial access token to avoid an initial refresh |

**Option 2: Static access token**

| Variable | Description |
|----------|-------------|
| `WHOOP_ACCESS_TOKEN` | A valid WHOOP API access token |

### Usage

Run the server directly:

```bash
whoop-client mcp
```

### Available Tools

| Tool | Description |
|------|-------------|
| `get_cycles` | List physiological cycles (paginated) |
| `get_cycle_by_id` | Get a single cycle |
| `get_cycle_sleep` | Get sleep for a cycle |
| `get_cycle_recovery` | Get recovery for a cycle |
| `get_recoveries` | List recovery records (paginated) |
| `get_sleeps` | List sleep records (paginated) |
| `get_sleep_by_id` | Get a single sleep record |
| `get_workouts` | List workout records (paginated) |
| `get_workout_by_id` | Get a single workout record |
| `get_profile` | Get basic user profile |
| `get_body_measurement` | Get body measurements |

## Development

```bash
uv sync              # Install dependencies
uv run pre-commit install  # Install pre-commit hooks
uv run pytest        # Run tests
uv run ruff check    # Lint
uv run ruff format   # Format
uv run ty check      # Type check
```

## License

MIT
