Installation
npm install foxreach
fetch). Zero runtime dependencies.
Quick Start
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
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
});
export FOXREACH_API_KEY=otr_your_api_key
const client = new FoxReach({ apiKey: process.env.FOXREACH_API_KEY! });
Resources
Leads
// 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");
// Get activity timeline and campaign memberships
const activity = await client.leads.activity("cld_abc123");
for (const item of activity.activities) {
console.log(`${item.type}: ${item.subject} at ${item.timestamp}`);
}
for (const membership of activity.campaigns) {
console.log(`Campaign: ${membership.campaignName} — ${membership.status}`);
}
Campaigns
// 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");
await client.campaigns.resume("cmp_xyz789"); // resume a paused campaign
// Add leads and accounts
await client.campaigns.addLeads("cmp_xyz789", ["cld_1", "cld_2"]);
await client.campaigns.addAccounts("cmp_xyz789", ["acc_1"]);
// Remove leads and accounts (campaign must not be active)
await client.campaigns.removeLead("cmp_xyz789", "cld_1");
await client.campaigns.removeAccount("cmp_xyz789", "acc_1");
// Delete (must be draft)
await client.campaigns.delete("cmp_xyz789");
Sequences
// 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
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
const accounts = await client.emailAccounts.list();
const account = await client.emailAccounts.get("acc_abc123");
await client.emailAccounts.delete("acc_abc123");
Inbox
// List unread threads
const threads = await client.inbox.listThreads({ isRead: false, category: "interested" });
// Get a thread
const thread = await client.inbox.get("rpl_abc123");
// Get the full conversation (all sent and received messages)
const conversation = await client.inbox.getConversation("rpl_abc123");
for (const msg of conversation.messages) {
console.log(`[${msg.direction}] ${msg.subject}: ${msg.body.slice(0, 100)}`);
}
// Send a reply
const result = await client.inbox.sendReply("rpl_abc123", {
body: "Thanks for your interest! Let's schedule a call.",
});
console.log(`Sent: ${result.messageId}`);
// Mark as read and categorize
await client.inbox.update("rpl_abc123", { isRead: true, category: "interested" });
// Get inbox stats
const stats = await client.inbox.stats();
console.log(`Total: ${stats.total}, Unread: ${stats.unread}, Interested: ${stats.interested}`);
Analytics
// 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`);
}
Webhooks
// List available event types
const eventTypes = await client.webhooks.listEventTypes();
console.log(eventTypes); // ["email.sent", "reply.received", ...]
// List webhooks
const page = await client.webhooks.list({ page: 1, pageSize: 20 });
for (const wh of page) {
console.log(`${wh.id}: ${wh.url} (active=${wh.isActive})`);
}
// Create a webhook (secret is only returned on creation)
const webhook = await client.webhooks.create({
url: "https://your-app.com/webhooks/outreach",
events: ["email.sent", "reply.received", "campaign.completed"],
isActive: true,
});
console.log(`Created: ${webhook.id}, Secret: ${webhook.secret}`); // save the secret!
// Update a webhook
await client.webhooks.update("cwh_abc123", {
events: ["email.sent", "lead.created"],
});
// Delete a webhook
await client.webhooks.delete("cwh_abc123");
Pagination
List endpoints returnPaginatedResponse objects:
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
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:import type { Lead, Campaign, Template, EmailAccount, Thread, OverviewStats, Webhook } 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