Skip to content
API & MCP

Your outreach, from your own code.

Everything the app does goes through the same REST API. Call it with an API key, receive signed webhooks when leads accept or reply, and let AI assistants work with your campaigns over MCP.

  • REST API

    Leads, lists, campaigns, the inbox, reviews and reports. JSON in, JSON out.

  • Signed webhooks

    Five events, HMAC-signed, retried for about two hours, with a delivery log.

  • REST hooks

    The subscribe/unsubscribe flow Zapier, Make and n8n use, open to any tool.

  • MCP server

    Hosted tools for Claude, ChatGPT or any MCP client, with every limit still applied.

Authentication

Owners and admins create API keys in Settings → Developers. A key is shown once; only its hash is stored. Send it as a bearer token:

Request
curl https://outember.io/v1/campaigns \
  -H "Authorization: Bearer oa_live_…"
  • A key belongs to one workspace and acts as a member of it: it can import leads, create and run campaigns, read the inbox and reply. It can't manage the team, billing, integrations or other keys.
  • Rate limit: 600 requests per minute per key.
  • Revoking a key stops it within a minute.
  • Your exact API address is shown in Settings → Developers.

Errors

Errors always have the same shape, and message is written for people, so you can show it as is. fields names the inputs that need fixing.

Error response
{ "error": { "code": "VALIDATION", "message": "Name your campaign", "fields": { "name": "Name your campaign" } } }

Lists are paginated with a cursor: pass the nextCursor you received back as ?cursor=.

Main endpoints

Main API endpoints
WhatCall
Create a lead listPOST /v1/lists { "name": "Q4 founders" }
Add leads (up to 1,000 per call)POST /v1/lists/:id/leads { "rows": [{ "profileUrl": "…", "firstName": "Sara", "company": "Acme", "email": "…" }] }
Import a LinkedIn or Sales Navigator searchPOST /v1/lists/:id/import-url { "url": "…", "accountId": "…", "maxLeads": 500 }
Import from any lead sourcePOST /v1/lists/:id/import-source with kind: search_url, post_engagers, followers, connections or invitations_received
Keep a list updated on a schedulePOST /v1/lists/:id/auto-imports (same body + intervalHours); PATCH/DELETE /v1/auto-imports/:id; run now with POST /v1/auto-imports/:id/run
Find emails for a listPOST /v1/lists/:id/find-emails
All leads, by search or tagGET /v1/leads?q=acme&tag=Dubai · GET /v1/leads/tags · GET /v1/leads.csv
One lead with campaigns, conversations and notesGET /v1/leads/:id; edit with PATCH /v1/leads/:id; notes POST /v1/leads/:id/notes
Bulk tag, do-not-contact, remove from listPOST /v1/leads/bulk { "leadIds": […], "addTags": ["Q4"], "addToDnc": true }
Do-not-contact listGET /v1/dnc · POST /v1/dnc { "entries": ["ceo@acme.com", "acme.com"] } · DELETE /v1/dnc/:id · GET /v1/dnc.csv
Campaigns with funnel statsGET /v1/campaigns
Add a list to a campaignPOST /v1/campaigns/:id/leads { "listId": "…" }
Launch, pause, resumePOST /v1/campaigns/:id/launch / pause / resume
Leads in a campaignGET /v1/campaigns/:id/leads?status=replied&q=sara · GET /v1/campaigns/:id/leads.csv
Per-step resultsGET /v1/campaigns/:id/steps
Put a lead back in the sequencePOST /v1/campaigns/:id/leads/:campaignLeadId/resume
ConversationsGET /v1/inbox/conversations?intent=interested
Messages in a conversationGET /v1/inbox/conversations/:id/messages
Reply (from the account the lead talked to)POST /v1/inbox/conversations/:id/messages { "text": "…" }, with up to 5 attachments of 8 MB
Start a conversationPOST /v1/inbox/conversations { "channel": "linkedin", "accountId": "…", "profileUrl": "…", "text": "…" }
Schedule a messagePOST /v1/inbox/conversations/:id/scheduled { "text": "…", "sendAt": "2026-11-12T09:00:00Z", "cancelIfReply": true }; cancel with DELETE /v1/inbox/scheduled/:id
AI drafts waiting for approvalGET /v1/review; approve with POST /v1/review/:id/approve
Withdraw old invitationsPOST /v1/accounts/:id/withdraw-invites { "olderThanDays": 30 }
Check a sending domainPOST /v1/mailboxes/domain-check { "domain": "acme.com" }
Saved sequence templatesGET/POST /v1/templates, PATCH/DELETE /v1/templates/:id
Monthly report (JSON or CSV)GET /v1/reports/monthly?month=2026-11 · GET /v1/reports/monthly.csv?month=2026-11
Team leaderboardGET /v1/team/leaderboard?days=30 · GET /v1/team/leaderboard.csv?days=30

Webhooks

Add an endpoint in Settings → Developers → Webhooks and choose the events. Each delivery is an HTTP POST with a JSON body:

Delivery body
{
  "id": "0193…",
  "type": "reply.classified",
  "occurredAt": "2026-11-09T10:31:00.000Z",
  "workspaceId": "…",
  "lead": { "id": "…", "firstName": "Sara", "lastName": "Khan", "company": "Acme", "title": "Head of Growth",
            "headline": "…", "location": "Dubai", "email": "sara@acme.io", "profileUrl": "https://www.linkedin.com/in/…" },
  "campaign": { "id": "…", "name": "Q4 founders" },
  "data": { "intent": "interested", "confidence": 0.92, "text": "Sounds good, what times work?", "conversationId": "…" }
}
Webhook events
EventWhendata
connection.acceptedA lead accepted the connection request—
reply.receivedA lead replied on LinkedIn or by emailchannel, text, automatic, conversationId
reply.classifiedThe reply assistant classified a replyintent, confidence, text, conversationId
lead.finishedA lead left the sequencestatus (completed, not_accepted, failed), reason
email.foundThe email finder found an addressemail, source, verified

Verify the signature

Every delivery carries three headers: X-Outreach-Event (the event type), X-Outreach-Delivery (the delivery id, the same on retries) and X-Outreach-Signature: t=<unix seconds>,v1=<hex HMAC-SHA256 of "<t>.<raw body>">, keyed with the endpoint's signing secret (shown once when you create it). Check it before trusting the body:

Node.js
import { createHmac, timingSafeEqual } from "node:crypto";

function verify(secret, rawBody, header, toleranceSeconds = 300) {
  const { t, v1 } = Object.fromEntries(header.split(",").map((kv) => kv.split("=")));
  if (Math.abs(Date.now() / 1000 - Number(t)) > toleranceSeconds) return false;
  const expected = createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex");
  return v1.length === expected.length && timingSafeEqual(Buffer.from(v1), Buffer.from(expected));
}

Delivery rules

  • Answer with any 2xx within 15 seconds. Redirects aren't followed.
  • Failures are retried about 8 times over roughly 2 hours, with backoff. A 4xx other than 408 and 429 isn't retried.
  • Every attempt is logged in Settings → Developers (status, response code, error), and you can retry a delivery from there.
  • Each event is delivered once per endpoint. Use id to make your handler idempotent anyway.
  • In production, endpoints must use https and resolve to a public address.

REST hooks (Zapier, Make, n8n)

Our Zapier, Make and n8n apps subscribe to events with REST hooks and an API key. Marketplace listings are coming soon; until then, any tool can use the same calls:

REST hook endpoints
WhatCall
Check the key (and its workspace)GET /v1/hooks/me
SubscribePOST /v1/hooks { "url": "https://hooks.zapier.com/…", "event": "reply.classified", "source": "zapier" } → { "id": "…" }
UnsubscribeDELETE /v1/hooks/:id
Sample payloadsGET /v1/hooks/sample?event=reply.classified
Active subscriptionsGET /v1/hooks

A subscription is a webhook endpoint like any other: same payloads, signature, retries and delivery log, labelled with its source.

AI assistants (MCP)

The API serves a Model Context Protocol server over Streamable HTTP at https://outember.io/mcp (your exact URL is in Settings → Integrations). Authenticate with an API key in the Authorization: Bearer oa_live_… header. For example, in Claude Code:

Claude Code
claude mcp add --transport http outember https://outember.io/mcp \
  --header "Authorization: Bearer oa_live_…"

Tools: the overview, campaigns and their stats, pause and resume, conversations and messages, reply, lead lists, find and add leads, add a list to a campaign, and the do-not-contact list. Tools that act on real people tell the assistant to confirm with you first, and every safety limit still applies. A local (stdio) version of the same tools exists for development; ask us if you need it.

CRM sync

HubSpot and Pipedrive connect in Settings → Integrations with a private app token or an API token; Salesforce with Connect (OAuth). Pick when to sync: when a lead replies, when a reply is interested, or when a connection is accepted. The lead is found by email (or name and company), created if missing (a Salesforce Lead), and the reply is added as a note (a Salesforce Task). No code needed.

Request and response shapes match what the app itself sends and receives. Missing something, or building an integration we should know about? Write to us.

Build on it today. Keys are in every plan.

The API, webhooks and the MCP server are included in the free trial and every paid plan.