Installation
Copy
npm install foxreach
fetch). Zero runtime dependencies.
Quick Start
Copy
import { FoxReach } from "foxreach";
const client = new FoxReach({ apiKey: "otr_your_api_key" });
// Create a lead
const lead = await client.leads.create({
email: "[email protected]",
firstName: "Jane",
lastName: "Smith",
company: "Acme Corp",
});
console.log(`Created: ${lead.id}`);
// List leads
const page = await client.leads.list({ search: "jane", pageSize: 25 });
for (const lead of page) {
console.log(`${lead.email} — ${lead.company}`);
}
// Check pagination
console.log(`Page ${page.meta.page}/${page.meta.totalPages}, ${page.meta.total} total`);
Configuration
Copy
const client = new FoxReach({
apiKey: "otr_...", // required
baseUrl: "https://api.foxreach.io/api/v1", // default
timeout: 30000, // ms, default
maxRetries: 3, // retries on 429, default
fetch: customFetch, // optional, for edge runtimes
});
Copy
export FOXREACH_API_KEY=otr_your_api_key
Copy
const client = new FoxReach({ apiKey: process.env.FOXREACH_API_KEY! });
Resources
Leads
Copy
// List with filters
const page = await client.leads.list({ page: 1, pageSize: 50, search: "acme", status: "active" });
// Get by ID
const lead = await client.leads.get("cld_abc123");
// Create
const newLead = await client.leads.create({
email: "[email protected]",
firstName: "Jane",
company: "Acme Corp",
tags: ["enterprise"],
});
// Update
await client.leads.update("cld_abc123", { company: "New Corp" });
// Delete
await client.leads.delete("cld_abc123");
Campaigns
Copy
// List
const campaigns = await client.campaigns.list({ status: "active" });
// Create (starts in draft)
const campaign = await client.campaigns.create({
name: "Q1 Outreach",
timezone: "America/New_York",
dailyLimit: 50,
});
// Update
await client.campaigns.update("cmp_xyz789", { dailyLimit: 100 });
// Lifecycle
await client.campaigns.start("cmp_xyz789");
await client.campaigns.pause("cmp_xyz789");
// Add leads and accounts
await client.campaigns.addLeads("cmp_xyz789", ["cld_1", "cld_2"]);
await client.campaigns.addAccounts("cmp_xyz789", ["acc_1"]);
// Delete (must be draft)
await client.campaigns.delete("cmp_xyz789");
Sequences
Copy
// List steps for a campaign
const steps = await client.campaigns.sequences.list("cmp_xyz789");
// Add a step
const step = await client.campaigns.sequences.create("cmp_xyz789", {
subject: "Quick question about {{company}}",
body: "Hi {{firstName}}, ...",
delayDays: 0,
});
// Update
await client.campaigns.sequences.update("cmp_xyz789", "seq_1", { delayDays: 3 });
// Delete
await client.campaigns.sequences.delete("cmp_xyz789", "seq_1");
Templates
Copy
const page = await client.templates.list();
const template = await client.templates.get("tpl_abc123");
const created = await client.templates.create({ name: "Cold Intro", body: "Hi {{firstName}}" });
await client.templates.update("tpl_abc123", { name: "Updated name" });
await client.templates.delete("tpl_abc123");
Email Accounts
Copy
const accounts = await client.emailAccounts.list();
const account = await client.emailAccounts.get("acc_abc123");
await client.emailAccounts.delete("acc_abc123");
Inbox
Copy
// List unread threads
const threads = await client.inbox.listThreads({ isRead: false, category: "interested" });
// Get a thread
const thread = await client.inbox.get("rpl_abc123");
// Mark as read and categorize
await client.inbox.update("rpl_abc123", { isRead: true, category: "interested" });
Analytics
Copy
// Dashboard overview
const overview = await client.analytics.overview();
console.log(`Leads: ${overview.totalLeads}, Reply rate: ${overview.replyRate}%`);
// Campaign stats with daily breakdown
const stats = await client.analytics.campaign("cmp_xyz789");
console.log(`Sent: ${stats.sent}, Replied: ${stats.replied}`);
for (const day of stats.dailyStats) {
console.log(` ${day.date}: ${day.sent} sent, ${day.replied} replied`);
}
Pagination
List endpoints returnPaginatedResponse objects:
Copy
const page = await client.leads.list({ page: 1, pageSize: 50 });
// Iterate current page
for (const lead of page) {
console.log(lead.email);
}
// Check pagination metadata
console.log(`Page ${page.meta.page}/${page.meta.totalPages}`);
// Get next page
if (page.hasNextPage()) {
const nextPage = await page.nextPage();
}
// Auto-paginate through ALL results (async iterator)
for await (const lead of (await client.leads.list({ pageSize: 100 })).autoPagingIter()) {
console.log(lead.email);
}
Error Handling
Copy
import {
FoxReach,
FoxReachError,
AuthenticationError,
NotFoundError,
RateLimitError,
ValidationError,
BadRequestError,
ServerError,
} from "foxreach";
try {
const lead = await client.leads.get("cld_nonexistent");
} catch (err) {
if (err instanceof NotFoundError) {
console.log("Lead not found");
} else if (err instanceof AuthenticationError) {
console.log("Invalid API key");
} else if (err instanceof RateLimitError) {
console.log(`Rate limited. Retry after ${err.retryAfter}s`);
} else if (err instanceof ValidationError) {
console.log(`Validation: ${err.message}`);
} else if (err instanceof ServerError) {
console.log(`Server error (${err.statusCode})`);
} else if (err instanceof FoxReachError) {
console.log(`API error: ${err.message}`);
}
}
The SDK automatically retries on
429 Too Many Requests responses (up to maxRetries times) with the delay specified in the Retry-After header.Type Safety
The SDK provides full TypeScript types for all resources:Copy
import type { Lead, Campaign, Template, EmailAccount, Thread, OverviewStats } from "foxreach";
// All responses are fully typed
const lead: Lead = await client.leads.get("cld_...");
lead.email; // string
lead.company; // string | null
lead.tags; // string[]
lead.createdAt; // string | null (ISO 8601)
Source Code
- GitHub: foxreach/foxreach-typescript-sdk
- npm:
npm install foxreach