> ## Documentation Index
> Fetch the complete documentation index at: https://docs.foxreach.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Python SDK

> Official Python SDK for the FoxReach API. Sync and async support, typed models, auto-pagination.

## Installation

<CodeGroup>
  ```bash pip theme={null}
  pip install foxreach
  ```

  ```bash poetry theme={null}
  poetry add foxreach
  ```

  ```bash pipenv theme={null}
  pipenv install foxreach
  ```
</CodeGroup>

**Requirements:** Python 3.9+

## Quick Start

```python theme={null}
from foxreach import FoxReach, LeadCreate

client = FoxReach(api_key="otr_your_api_key")

# Create a lead
lead = client.leads.create(LeadCreate(
    email="jane@example.com",
    first_name="Jane",
    last_name="Smith",
    company="Acme Corp",
))
print(f"Created: {lead.id}")

# List leads
page = client.leads.list(search="jane", page_size=25)
for lead in page:
    print(f"{lead.email} — {lead.company}")

# Check pagination info
print(f"Page {page.meta.page}/{page.meta.total_pages}, {page.meta.total} total")

client.close()
```

## Configuration

```python theme={null}
client = FoxReach(
    api_key="otr_...",                                  # required
    base_url="https://api.foxreach.io/api/v1",          # default
    timeout=30.0,                                        # seconds, default
    max_retries=3,                                       # retries on 429, default
)
```

You can also use environment variables:

```bash theme={null}
export FOXREACH_API_KEY=otr_your_api_key
```

```python theme={null}
import os
from foxreach import FoxReach

client = FoxReach(api_key=os.environ["FOXREACH_API_KEY"])
```

## Resources

### Leads

```python theme={null}
from foxreach import LeadCreate, LeadUpdate

# List with filters
page = client.leads.list(page=1, page_size=50, search="acme", status="active")

# Get by ID
lead = client.leads.get("cld_abc123")

# Create
lead = client.leads.create(LeadCreate(
    email="jane@example.com",
    first_name="Jane",
    company="Acme Corp",
    tags=["enterprise"],
))

# Update
lead = client.leads.update("cld_abc123", LeadUpdate(company="New Corp"))

# Delete
client.leads.delete("cld_abc123")

# Get activity timeline and campaign memberships
activity = client.leads.activity("cld_abc123")
for item in activity.activities:
    print(f"{item.type}: {item.subject} at {item.timestamp}")
for membership in activity.campaigns:
    print(f"Campaign: {membership.campaign_name} — {membership.status}")
```

### Campaigns

```python theme={null}
from foxreach import CampaignCreate, CampaignUpdate

# List
page = client.campaigns.list(status="active")

# Create (starts in draft)
campaign = client.campaigns.create(CampaignCreate(
    name="Q1 Outreach",
    timezone="America/New_York",
    daily_limit=50,
))

# Update
client.campaigns.update("cmp_xyz789", CampaignUpdate(daily_limit=100))

# Lifecycle
client.campaigns.start("cmp_xyz789")
client.campaigns.pause("cmp_xyz789")
client.campaigns.resume("cmp_xyz789")  # resume a paused campaign

# Add leads and accounts
client.campaigns.add_leads("cmp_xyz789", ["cld_1", "cld_2"])
client.campaigns.add_accounts("cmp_xyz789", ["acc_1"])

# Remove leads and accounts (campaign must not be active)
client.campaigns.remove_lead("cmp_xyz789", "cld_1")
client.campaigns.remove_account("cmp_xyz789", "acc_1")

# Delete (must be draft)
client.campaigns.delete("cmp_xyz789")
```

### Sequences

```python theme={null}
from foxreach import SequenceCreate, SequenceUpdate

# List steps for a campaign
steps = client.campaigns.sequences.list("cmp_xyz789")

# Add a step
step = client.campaigns.sequences.create("cmp_xyz789", SequenceCreate(
    subject="Quick question about {{company}}",
    body="Hi {{firstName}}, ...",
    delay_days=0,
))

# Update
client.campaigns.sequences.update("cmp_xyz789", "seq_1", SequenceUpdate(delay_days=3))

# Delete
client.campaigns.sequences.delete("cmp_xyz789", "seq_1")
```

### Templates

```python theme={null}
from foxreach import TemplateCreate, TemplateUpdate

page = client.templates.list()
template = client.templates.get("tpl_abc123")
template = client.templates.create(TemplateCreate(name="Cold Intro", body="Hi {{firstName}}"))
client.templates.update("tpl_abc123", TemplateUpdate(name="Updated name"))
client.templates.delete("tpl_abc123")
```

### Email Accounts

```python theme={null}
page = client.email_accounts.list()
account = client.email_accounts.get("acc_abc123")
client.email_accounts.delete("acc_abc123")
```

### Inbox

```python theme={null}
from foxreach import ThreadUpdate

# List unread threads
threads = client.inbox.list_threads(is_read=False, category="interested")

# Get a thread
thread = client.inbox.get("rpl_abc123")

# Get the full conversation (all sent and received messages)
conversation = client.inbox.get_conversation("rpl_abc123")
for msg in conversation.messages:
    print(f"[{msg.direction}] {msg.subject}: {msg.body[:100]}")

# Send a reply
result = client.inbox.send_reply("rpl_abc123", body="Thanks for your interest! Let's schedule a call.")
print(f"Sent: {result.message_id}")

# Mark as read and categorize
client.inbox.update("rpl_abc123", ThreadUpdate(is_read=True, category="interested"))

# Get inbox stats
stats = client.inbox.stats()
print(f"Total: {stats.total}, Unread: {stats.unread}, Interested: {stats.interested}")
```

### Analytics

```python theme={null}
# Dashboard overview
overview = client.analytics.overview()
print(f"Leads: {overview.total_leads}, Reply rate: {overview.reply_rate}%")

# Campaign stats with daily breakdown
stats = client.analytics.campaign("cmp_xyz789")
print(f"Sent: {stats.sent}, Replied: {stats.replied}")
for day in stats.daily_stats:
    print(f"  {day.date}: {day.sent} sent, {day.replied} replied")
```

### Webhooks

```python theme={null}
# List available event types
event_types = client.webhooks.list_event_types()
print(event_types)  # ["email.sent", "reply.received", ...]

# List webhooks
page = client.webhooks.list(page=1, page_size=20)
for wh in page:
    print(f"{wh.id}: {wh.url} (active={wh.is_active})")

# Create a webhook (secret is only returned on creation)
webhook = client.webhooks.create(
    url="https://your-app.com/webhooks/outreach",
    events=["email.sent", "reply.received", "campaign.completed"],
    is_active=True,
)
print(f"Created: {webhook.id}, Secret: {webhook.secret}")  # save the secret!

# Update a webhook
webhook = client.webhooks.update("cwh_abc123", events=["email.sent", "lead.created"])

# Delete a webhook
client.webhooks.delete("cwh_abc123")
```

## Pagination

List endpoints return `PaginatedResponse` objects:

```python theme={null}
page = client.leads.list(page=1, page_size=50)

# Iterate current page
for lead in page:
    print(lead.email)

# Check pagination metadata
print(f"Page {page.meta.page}/{page.meta.total_pages}")

# Get next page
if page.has_next_page():
    next_page = page.next_page()

# Auto-paginate through ALL results
for lead in client.leads.list(page_size=100).auto_paging_iter():
    print(lead.email)
```

## Async Support

All operations are available asynchronously via `AsyncFoxReach`:

```python theme={null}
import asyncio
from foxreach import AsyncFoxReach, LeadCreate

async def main():
    async with AsyncFoxReach(api_key="otr_your_api_key") as client:
        # Create a lead
        lead = await client.leads.create(LeadCreate(email="jane@example.com"))

        # Auto-paginate
        async for lead in (await client.leads.list()).auto_paging_iter():
            print(lead.email)

        # Analytics
        overview = await client.analytics.overview()
        print(f"Total leads: {overview.total_leads}")

asyncio.run(main())
```

## Error Handling

```python theme={null}
from foxreach import (
    FoxReach,
    FoxReachError,
    AuthenticationError,
    NotFoundError,
    RateLimitError,
    ValidationError,
    BadRequestError,
    ServerError,
    ConnectionError,
)

try:
    lead = client.leads.get("cld_nonexistent")
except NotFoundError:
    print("Lead not found")
except AuthenticationError:
    print("Invalid API key")
except RateLimitError as e:
    print(f"Rate limited. Retry after {e.retry_after}s")
except ValidationError as e:
    print(f"Validation failed: {e.response_body}")
except ServerError as e:
    print(f"Server error ({e.status_code})")
except FoxReachError as e:
    print(f"API error: {e}")
```

<Tip>
  The SDK automatically retries on `429 Too Many Requests` responses (up to `max_retries` times) with the delay specified in the `Retry-After` header.
</Tip>

## Source Code

* **GitHub:** [foxreach/foxreach-python-sdk](https://github.com/foxreach/foxreach-python-sdk)
* **PyPI:** `pip install foxreach`
