Skip to main content

Installation

pip install foxreach
Requirements: Python 3.9+

Quick Start

from foxreach import FoxReach, LeadCreate

client = FoxReach(api_key="otr_your_api_key")

# Create a lead
lead = client.leads.create(LeadCreate(
    email="[email protected]",
    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

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:
export FOXREACH_API_KEY=otr_your_api_key
import os
from foxreach import FoxReach

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

Resources

Leads

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="[email protected]",
    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

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

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

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

page = client.email_accounts.list()
account = client.email_accounts.get("acc_abc123")
client.email_accounts.delete("acc_abc123")

Inbox

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

# 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

# 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:
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:
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="[email protected]"))

        # 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

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}")
The SDK automatically retries on 429 Too Many Requests responses (up to max_retries times) with the delay specified in the Retry-After header.

Source Code