Developers38 endpoints · 37 objectsOpenAPI 3.0

MailFleet API reference

A two-way REST API over your workspace — read campaigns, leads, replies and stats, and create campaigns, add leads and reply to leads from your own tools. JSON in, JSON out, one bearer token, 120 requests a minute.

Authentication

Create a key in Settings → API & webhooks and choose its access: Read & write (read everything, and make changes — create campaigns, add leads, reply to leads) or Read only (for dashboards). Keys created before the API could write are read-only; a change sent with one is answered 403 read_only_key. Keys start with sk_live_ and are shown once — only a hash is stored, so a lost key is revoked and replaced, never recovered. Treat a key as a password: a read & write key can email your leads.

curl -s "https://mailfleet.co/api/v1/stats" \
  -H "Authorization: Bearer sk_live_your_key"

An X-API-Key: sk_live_… header works identically if a bearer token is awkward in your client.

API access is a Pro and Scale feature. A key can be created on any plan, but requests from a Trial or Basic workspace are answered with 403. GET /api/v1 needs no key at all and lists what is available.

Conventions

  • Base URL — https://mailfleet.co/api/v1. Every path below is relative to it.
  • Field names are snake_case (first_name, sent_today, daily_limit).
  • Lists accept limit (1–100, default 25) and offset (default 0), and answer with { object: "list", data: [...], pagination: { total, limit, offset, has_more } }.
  • Scope — a key only ever sees its own workspace. There is no cross-workspace read, for anyone.
  • Reads and writes — GET reads; POST, PUT, PATCH and DELETE change things and need a read & write key. Every change runs the same code as the app, so it behaves exactly as clicking it would. To be told when something happens rather than polling for it, use webhooks — on every plan, and manageable through the API.
  • Safe retries — send an Idempotency-Key header (a UUID) with a request that creates or sends something. A retry with the same key and body gets the first response back (with Idempotent-Replayed: true) instead of doing it twice. Keys last 24 hours.
  • Bodies are JSON (Content-Type: application/json), up to 5 MB — 2,000 leads with custom fields fit easily.
  • Machine-readable — the OpenAPI 3.0 document describes everything on this page; point your generator at it.

GET /campaigns

List campaigns

Parameters

ParameterTypeInNotes
limitintegerqueryPage size, 1–100 (default 25). Default 25.
offsetintegerqueryNumber of items to skip (default 0). Default 0.
statusDRAFT · SENDING · PAUSED · FINISHEDqueryFilter by status.

Request

curl -s "https://mailfleet.co/api/v1/campaigns?status=SENDING" \
  -H "Authorization: Bearer sk_live_your_key"

Response

{
  "object": "list",
  "data": [
    {
      "object": "campaign",
      "id": "cmp_9f2a1c",
      "name": "Acme — Q4 outbound",
      "status": "SENDING",
      "stats": {
        "leads": 2400,
        "sent": 1890,
        "opened": 812,
        "clicked": 96,
        "replied": 74,
        "interested": 21,
        "bounced": 18,
        "skipped": 12,
        "delivered": 128,
        "hard_bounced": 128,
        "blocked": 128,
        "leads_reached": 128,
        "auto_replied": 128
      },
      "started_at": "2026-09-08T08:02:11.000Z",
      "created_at": "2026-09-05T16:41:57.000Z"
    }
  ],
  "pagination": {
    "total": 214,
    "limit": 25,
    "offset": 0,
    "has_more": true
  }
}

POST /campaigns

Create a campaign

Create a draft email campaign — with its steps, sender_ids and schedule in the same call if you have them, or set each later. It starts with no steps (never a placeholder email) and sends nothing until you start it with POST /campaigns/{id}/start, which runs the same checks as Launch in the app.

Needs a read & write key. Send an Idempotency-Key so a retried request can't create it twice.

Parameters

ParameterTypeInNotes
Idempotency-KeystringheaderOptional: a unique key (a UUID) so a retry of this request replays the first response instead of doing it twice. Kept for 24 hours.

Body

FieldTypeNotes
namestringrequiredThe campaign's name.
stepsarray of objectoptionalThe sequence, in order (see PUT /campaigns/{id}/steps).
sender_idsarray of stringoptionalMailboxes to send from — ids from GET /senders. Empty or left out = every healthy sender.
scheduleobjectoptional

Request

curl -s -X POST "https://mailfleet.co/api/v1/campaigns" \
  -H "Authorization: Bearer sk_live_your_key" \
  -H "Content-Type: application/json" \
  -d '{"name":"Acme — Q4 outbound","steps":[{"type":"email","subject":"Quick question about {{company}}","body":"Hi {{first_name|there}},\n\nNoticed {{company}} is hiring SDRs — worth a short call?"},{"type":"email","wait_days":3,"subject":"Re: Quick question about {{company}}","body":"Bumping this in case it got buried."}],"sender_ids":["snd_2e10b4"],"schedule":{"timezone":"America/New_York","days":["Mon","Tue","Wed","Thu","Fri"],"window_from":"09:00","window_to":"16:30"}}'

Response

{
  "object": "campaign",
  "id": "cmp_9f2a1c",
  "name": "Acme — Q4 outbound",
  "status": "DRAFT",
  "stats": {
    "leads": 2400,
    "sent": 1890,
    "opened": 812,
    "clicked": 96,
    "replied": 74,
    "interested": 21,
    "bounced": 18,
    "skipped": 12,
    "delivered": 128,
    "hard_bounced": 128,
    "blocked": 128,
    "leads_reached": 128,
    "auto_replied": 128
  },
  "started_at": "2026-09-22T09:14:02.000Z",
  "created_at": "2026-09-22T09:14:02.000Z",
  "type": "email",
  "step_count": 128,
  "sender_ids": [
    "…"
  ],
  "schedule": {
    "object": "schedule",
    "timezone": "America/New_York",
    "days": [
      "Mon",
      "Tue",
      "Wed",
      "Thu",
      "Fri"
    ],
    "window_from": "09:00",
    "window_to": "16:30",
    "gap_minutes": 20,
    "new_leads_per_day": 100,
    "respect_lead_timezone": false,
    "start_date": null
  }
}

GET /campaigns/{id}

Get a campaign

The campaign's stats, plus its type, schedule, senders (sender_ids; empty = every healthy sender) and how many steps it has.

Parameters

ParameterTypeInNotes
idstringrequiredThe campaign's id.

Request

curl -s "https://mailfleet.co/api/v1/campaigns/cmp_9f2a1c" \
  -H "Authorization: Bearer sk_live_your_key"

Response

{
  "object": "campaign",
  "id": "cmp_9f2a1c",
  "name": "Acme — Q4 outbound",
  "status": "DRAFT",
  "stats": {
    "leads": 2400,
    "sent": 1890,
    "opened": 812,
    "clicked": 96,
    "replied": 74,
    "interested": 21,
    "bounced": 18,
    "skipped": 12,
    "delivered": 128,
    "hard_bounced": 128,
    "blocked": 128,
    "leads_reached": 128,
    "auto_replied": 128
  },
  "started_at": "2026-09-22T09:14:02.000Z",
  "created_at": "2026-09-22T09:14:02.000Z",
  "type": "email",
  "step_count": 128,
  "sender_ids": [
    "…"
  ],
  "schedule": {
    "object": "schedule",
    "timezone": "America/New_York",
    "days": [
      "Mon",
      "Tue",
      "Wed",
      "Thu",
      "Fri"
    ],
    "window_from": "09:00",
    "window_to": "16:30",
    "gap_minutes": 20,
    "new_leads_per_day": 100,
    "respect_lead_timezone": false,
    "start_date": null
  }
}

PATCH /campaigns/{id}

Rename a campaign

Steps, senders, schedule, start and pause have their own endpoints.

Parameters

ParameterTypeInNotes
idstringrequiredThe campaign's id.

Body

FieldTypeNotes
namestringrequired

Request

curl -s -X PATCH "https://mailfleet.co/api/v1/campaigns/cmp_9f2a1c" \
  -H "Authorization: Bearer sk_live_your_key" \
  -H "Content-Type: application/json" \
  -d '{"name":"Acme — Q4 outbound (EU)"}'

Response

{
  "object": "campaign",
  "id": "cmp_9f2a1c",
  "name": "Acme — Q4 outbound",
  "status": "DRAFT",
  "stats": {
    "leads": 2400,
    "sent": 1890,
    "opened": 812,
    "clicked": 96,
    "replied": 74,
    "interested": 21,
    "bounced": 18,
    "skipped": 12,
    "delivered": 128,
    "hard_bounced": 128,
    "blocked": 128,
    "leads_reached": 128,
    "auto_replied": 128
  },
  "started_at": "2026-09-22T09:14:02.000Z",
  "created_at": "2026-09-22T09:14:02.000Z",
  "type": "email",
  "step_count": 128,
  "sender_ids": [
    "…"
  ],
  "schedule": {
    "object": "schedule",
    "timezone": "America/New_York",
    "days": [
      "Mon",
      "Tue",
      "Wed",
      "Thu",
      "Fri"
    ],
    "window_from": "09:00",
    "window_to": "16:30",
    "gap_minutes": 20,
    "new_leads_per_day": 100,
    "respect_lead_timezone": false,
    "start_date": null
  }
}

DELETE /campaigns/{id}

Delete a campaign

As Delete does in the app: its sequence, enrolments, record of sent emails and tasks go with it. The leads, their lists and the replies they sent stay (no longer linked to it).

Parameters

ParameterTypeInNotes
idstringrequiredThe campaign's id.

Request

curl -s -X DELETE "https://mailfleet.co/api/v1/campaigns/cmp_9f2a1c" \
  -H "Authorization: Bearer sk_live_your_key"

Response

{
  "object": "campaign",
  "id": "cmp_9f2a1c",
  "deleted": true
}

POST /campaigns/{id}/start

Start or resume a campaign

Start a draft or resume a paused campaign — the same checks as Launch in the app: steps with text, leads to send to, and an active sender. A refusal (400 not_ready) lists what's missing in error.blockers. Emails go out within the campaign's schedule. Starting one that's already sending changes nothing.

Parameters

ParameterTypeInNotes
idstringrequiredThe campaign's id.

Request

curl -s -X POST "https://mailfleet.co/api/v1/campaigns/cmp_9f2a1c/start" \
  -H "Authorization: Bearer sk_live_your_key"

Response

{
  "object": "campaign",
  "id": "cmp_9f2a1c",
  "name": "Acme — Q4 outbound",
  "status": "DRAFT",
  "stats": {
    "leads": 2400,
    "sent": 1890,
    "opened": 812,
    "clicked": 96,
    "replied": 74,
    "interested": 21,
    "bounced": 18,
    "skipped": 12,
    "delivered": 128,
    "hard_bounced": 128,
    "blocked": 128,
    "leads_reached": 128,
    "auto_replied": 128
  },
  "started_at": "2026-09-22T09:14:02.000Z",
  "created_at": "2026-09-22T09:14:02.000Z",
  "type": "email",
  "step_count": 128,
  "sender_ids": [
    "…"
  ],
  "schedule": {
    "object": "schedule",
    "timezone": "America/New_York",
    "days": [
      "Mon",
      "Tue",
      "Wed",
      "Thu",
      "Fri"
    ],
    "window_from": "09:00",
    "window_to": "16:30",
    "gap_minutes": 20,
    "new_leads_per_day": 100,
    "respect_lead_timezone": false,
    "start_date": null
  }
}

POST /campaigns/{id}/pause

Pause a campaign

Nothing more goes out until it's started again. Pausing a paused campaign changes nothing; a draft or finished one answers 409 not_sending.

Parameters

ParameterTypeInNotes
idstringrequiredThe campaign's id.

Request

curl -s -X POST "https://mailfleet.co/api/v1/campaigns/cmp_9f2a1c/pause" \
  -H "Authorization: Bearer sk_live_your_key"

Response

{
  "object": "campaign",
  "id": "cmp_9f2a1c",
  "name": "Acme — Q4 outbound",
  "status": "DRAFT",
  "stats": {
    "leads": 2400,
    "sent": 1890,
    "opened": 812,
    "clicked": 96,
    "replied": 74,
    "interested": 21,
    "bounced": 18,
    "skipped": 12,
    "delivered": 128,
    "hard_bounced": 128,
    "blocked": 128,
    "leads_reached": 128,
    "auto_replied": 128
  },
  "started_at": "2026-09-22T09:14:02.000Z",
  "created_at": "2026-09-22T09:14:02.000Z",
  "type": "email",
  "step_count": 128,
  "sender_ids": [
    "…"
  ],
  "schedule": {
    "object": "schedule",
    "timezone": "America/New_York",
    "days": [
      "Mon",
      "Tue",
      "Wed",
      "Thu",
      "Fri"
    ],
    "window_from": "09:00",
    "window_to": "16:30",
    "gap_minutes": 20,
    "new_leads_per_day": 100,
    "respect_lead_timezone": false,
    "start_date": null
  }
}

GET /campaigns/{id}/steps

Get a campaign's sequence

Every step in order, with the same fields PUT takes — read, edit, write back.

Parameters

ParameterTypeInNotes
idstringrequiredThe campaign's id.

Request

curl -s "https://mailfleet.co/api/v1/campaigns/cmp_9f2a1c/steps" \
  -H "Authorization: Bearer sk_live_your_key"

Response

{
  "object": "list",
  "data": [
    {
      "object": "step",
      "number": 1,
      "type": "email",
      "wait_days": 0,
      "subject": "Quick question about {{company}}",
      "body": "Hi {{first_name|there}},\n\nNoticed {{company}} is hiring SDRs — worth a short call?",
      "variants": [
        {
          "subject": "{{first_name}}, a quick one",
          "body": "Hi {{first_name}} — saw {{company}} is growing the SDR team…"
        }
      ],
      "due_days": 128,
      "note": "…"
    }
  ],
  "pagination": {
    "total": 214,
    "limit": 25,
    "offset": 0,
    "has_more": true
  }
}

PUT /campaigns/{id}/steps

Replace a campaign's sequence

Replaces every step with these, in order. type is email (sent automatically), manual (an email a person sends from Tasks, prefilled) or call (a call task — needs the calling add-on). Personalise with {{first_name}}, {{company}}, {{title}} … or {{first_name|there}} for a fallback; a lead's custom fields are variables too, so {{email_1_body}} sends each lead their own copy. Blank lines make paragraphs; the mailbox signature is added. Up to 12 steps, 4 A/B variants each; the first step's wait_days is always 0.

Email campaigns only — a Multi Channel or LinkedIn sequence is edited in the app. On a sending campaign this changes what its remaining leads receive, and leads who had finished continue into steps added at the end, as saving in the app does.

Parameters

ParameterTypeInNotes
idstringrequiredThe campaign's id.
Idempotency-KeystringheaderOptional: a unique key (a UUID) so a retry of this request replays the first response instead of doing it twice. Kept for 24 hours.

Body

FieldTypeNotes
stepsarray of objectrequiredThe whole sequence, in order.

Request

curl -s -X PUT "https://mailfleet.co/api/v1/campaigns/cmp_9f2a1c/steps" \
  -H "Authorization: Bearer sk_live_your_key" \
  -H "Content-Type: application/json" \
  -d '{"steps":[{"type":"email","subject":"{{email_1_subject}}","body":"{{email_1_body}}"},{"type":"email","wait_days":3,"subject":"Re: {{email_1_subject}}","body":"{{email_2_body}}"},{"type":"call","wait_days":2,"subject":"Follow up on the emails","body":"Opener: I sent you a note about …","due_days":2}]}'

Response

{
  "object": "list",
  "data": [
    {
      "object": "step",
      "number": 1,
      "type": "email",
      "wait_days": 0,
      "subject": "Quick question about {{company}}",
      "body": "Hi {{first_name|there}},\n\nNoticed {{company}} is hiring SDRs — worth a short call?",
      "variants": [
        {
          "subject": "{{first_name}}, a quick one",
          "body": "Hi {{first_name}} — saw {{company}} is growing the SDR team…"
        }
      ],
      "due_days": 128,
      "note": "…"
    }
  ],
  "pagination": {
    "total": 214,
    "limit": 25,
    "offset": 0,
    "has_more": true
  }
}

GET /campaigns/{id}/senders

Get a campaign's senders

The mailboxes it sends from. An empty list = every healthy sender in the workspace.

Parameters

ParameterTypeInNotes
idstringrequiredThe campaign's id.

Request

curl -s "https://mailfleet.co/api/v1/campaigns/cmp_9f2a1c/senders" \
  -H "Authorization: Bearer sk_live_your_key"

Response

{
  "object": "list",
  "data": [
    {
      "object": "sender",
      "id": "snd_2e10b4",
      "email": "sam@outbound.acme.com",
      "name": "Sam Reed",
      "status": "ACTIVE"
    }
  ],
  "pagination": {
    "total": 214,
    "limit": 25,
    "offset": 0,
    "has_more": true
  }
}

PUT /campaigns/{id}/senders

Set a campaign's senders

Choose the mailboxes it sends from (ids from GET /senders); an empty list = every healthy sender. Ids that aren't in your workspace are left out and listed in not_found.

Parameters

ParameterTypeInNotes
idstringrequiredThe campaign's id.

Body

FieldTypeNotes
sender_idsarray of stringrequiredMailbox ids from GET /senders.

Request

curl -s -X PUT "https://mailfleet.co/api/v1/campaigns/cmp_9f2a1c/senders" \
  -H "Authorization: Bearer sk_live_your_key" \
  -H "Content-Type: application/json" \
  -d '{"sender_ids":["snd_2e10b4"]}'

Response

{
  "object": "list",
  "data": [
    {
      "object": "sender",
      "id": "snd_2e10b4",
      "email": "sam@outbound.acme.com",
      "name": "Sam Reed",
      "status": "ACTIVE"
    }
  ],
  "pagination": {
    "total": 214,
    "limit": 25,
    "offset": 0,
    "has_more": true
  }
}

GET /campaigns/{id}/schedule

Get a campaign's schedule

When and how fast it sends: timezone, days, the sending window, the gap between emails and new leads a day.

Parameters

ParameterTypeInNotes
idstringrequiredThe campaign's id.

Request

curl -s "https://mailfleet.co/api/v1/campaigns/cmp_9f2a1c/schedule" \
  -H "Authorization: Bearer sk_live_your_key"

Response

{
  "object": "schedule",
  "timezone": "America/New_York",
  "days": [
    "Mon",
    "Tue",
    "Wed",
    "Thu",
    "Fri"
  ],
  "window_from": "09:00",
  "window_to": "16:30",
  "gap_minutes": 20,
  "new_leads_per_day": 100,
  "respect_lead_timezone": false,
  "start_date": null
}

PATCH /campaigns/{id}/schedule

Change a campaign's schedule

Send only what changes; the rest keeps its value, as does every other setting (tracking, stop rules …), which stays as set in the app. The window is read in the campaign's timezone — or each lead's own, with respect_lead_timezone.

Parameters

ParameterTypeInNotes
idstringrequiredThe campaign's id.

Body

FieldTypeNotes
timezonestringoptional
daysarray of Mon · Tue · Wed · Thu · Fri · Sat · Sunoptional
window_fromstringoptional
window_tostringoptional
gap_minutesintegeroptional
new_leads_per_dayintegeroptional
respect_lead_timezonebooleanoptional
start_datestringoptionalYYYY-MM-DD, or "" to start straight away.

Request

curl -s -X PATCH "https://mailfleet.co/api/v1/campaigns/cmp_9f2a1c/schedule" \
  -H "Authorization: Bearer sk_live_your_key" \
  -H "Content-Type: application/json" \
  -d '{"timezone":"America/New_York","window_from":"08:30","window_to":"17:00","new_leads_per_day":60}'

Response

{
  "object": "schedule",
  "timezone": "America/New_York",
  "days": [
    "Mon",
    "Tue",
    "Wed",
    "Thu",
    "Fri"
  ],
  "window_from": "09:00",
  "window_to": "16:30",
  "gap_minutes": 20,
  "new_leads_per_day": 100,
  "respect_lead_timezone": false,
  "start_date": null
}

GET /campaigns/{id}/leads

List a campaign's leads

Who is in the campaign and where each is in the sequence, newest first.

Parameters

ParameterTypeInNotes
idstringrequiredThe campaign's id.
limitintegerqueryPage size, 1–100 (default 25). Default 25.
offsetintegerqueryNumber of items to skip (default 0). Default 0.
statusACTIVE · PAUSED · REPLIED · BOUNCED · UNSUBSCRIBED · FINISHEDqueryFilter by enrolment status.

Request

curl -s "https://mailfleet.co/api/v1/campaigns/cmp_9f2a1c/leads?status=ACTIVE" \
  -H "Authorization: Bearer sk_live_your_key"

Response

{
  "object": "list",
  "data": [
    {
      "object": "campaign_lead",
      "status": "ACTIVE",
      "step": 2,
      "waiting_on_task": false,
      "last_sent_at": "2026-09-22T09:14:02.000Z",
      "next_due_at": "2026-09-22T09:14:02.000Z",
      "enrolled_at": "2026-09-22T09:14:02.000Z",
      "lead": {
        "object": "lead",
        "id": "led_4b81e0",
        "email": "priya@acme.com",
        "first_name": "Priya",
        "last_name": "Raman",
        "company": "Acme",
        "title": "Head of Operations",
        "city": "Leeds",
        "website": "acme.com",
        "phone": "+44 7700 900123",
        "linkedin_url": "https://www.linkedin.com/in/example-lead",
        "status": "CONTACTED",
        "created_at": "2026-09-05T16:42:10.000Z"
      }
    }
  ],
  "pagination": {
    "total": 214,
    "limit": 25,
    "offset": 0,
    "has_more": true
  }
}

POST /campaigns/{id}/leads

Add leads to a campaign

Send leads (up to 2,000 per call) — they're saved as leads, deduped by email, into list_id or a new list — or just list_id to enrol a whole existing list. Only the people named are enrolled; others in a reused list aren't.

Like an import in the app: blocklisted and unsubscribed people are left out, anyone already in the campaign isn't added twice, new leads are added only while the workspace has room (the leads its plan keeps), and skip_active_elsewhere leaves out anyone still being emailed by another campaign. Enrolled leads start when the campaign is sending, within its schedule. Custom fields become {{variables}} for the copy.

Parameters

ParameterTypeInNotes
idstringrequiredThe campaign's id.
Idempotency-KeystringheaderOptional: a unique key (a UUID) so a retry of this request replays the first response instead of doing it twice. Kept for 24 hours.

Body

FieldTypeNotes
leadsarray of objectoptionalThe people to add (or send list_id alone).
list_idstringoptionalSave them into this existing list — or, without leads, enrol this whole list.
list_namestringoptionalName for the new list the leads go into (when there's no list_id).
skip_active_elsewherebooleanoptionalLeave out people still being emailed by another campaign.

Request

curl -s -X POST "https://mailfleet.co/api/v1/campaigns/cmp_9f2a1c/leads" \
  -H "Authorization: Bearer sk_live_your_key" \
  -H "Content-Type: application/json" \
  -d '{"leads":[{"email":"priya@acme.com","first_name":"Priya","company":"Acme","custom":{"email_1_subject":"Priya — the Leeds depot","email_1_body":"Congrats on opening the Leeds depot…"}},{"email":"tom@northwind.io","first_name":"Tom","company":"Northwind"}],"list_name":"Q4 — logistics"}'

Response

{
  "object": "enrollment",
  "campaign_id": "cmp_9f2a1c",
  "list_id": "lst_9a12c4",
  "imported": 180,
  "updated": 14,
  "skipped": 4,
  "blocked": 2,
  "enrolled": 192,
  "in_list": null,
  "skipped_active_elsewhere": 0,
  "campaign_status": "SENDING",
  "errors": [
    {
      "row": 7,
      "email": "not-an-address",
      "reason": "That isn't a valid email address."
    }
  ]
}

DELETE /campaigns/{id}/leads/{lead_id}

Remove a lead from a campaign

Nothing more is sent to them from this campaign; the lead, their lists and what was already sent stay. An email campaign's enrolment is deleted (adding them again starts at step 1); a Multi Channel or LinkedIn campaign's is ended instead (ended: true), so the campaign can't add them back on its own.

Parameters

ParameterTypeInNotes
idstringrequiredThe campaign's id.
lead_idstringrequiredThe lead's id.

Request

curl -s -X DELETE "https://mailfleet.co/api/v1/campaigns/cmp_9f2a1c/leads/led_4b81e0" \
  -H "Authorization: Bearer sk_live_your_key"

Response

{
  "object": "campaign_lead",
  "campaign_id": "cmp_9f2a1c",
  "lead_id": "led_4b81e0",
  "removed": true,
  "ended": false,
  "campaign_leads": 2399
}

GET /leads

List leads

Parameters

ParameterTypeInNotes
limitintegerqueryPage size, 1–100 (default 25). Default 25.
offsetintegerqueryNumber of items to skip (default 0). Default 0.
statusNEW · CONTACTED · REPLIED · MEETING · WON · BOUNCED · UNSUBSCRIBED · BLOCKEDqueryFilter by status.
searchstringqueryCase-insensitive match on email, company or name.
emailstringqueryOne exact address — find a lead to update.
list_idstringqueryOnly the leads in this list.

Request

curl -s "https://mailfleet.co/api/v1/leads?status=CONTACTED" \
  -H "Authorization: Bearer sk_live_your_key"

Response

{
  "object": "list",
  "data": [
    {
      "object": "lead",
      "id": "led_4b81e0",
      "email": "priya@acme.com",
      "first_name": "Priya",
      "last_name": "Raman",
      "company": "Acme",
      "title": "Head of Operations",
      "city": "Leeds",
      "website": "acme.com",
      "phone": "+44 7700 900123",
      "linkedin_url": "https://www.linkedin.com/in/example-lead",
      "status": "CONTACTED",
      "created_at": "2026-09-05T16:42:10.000Z"
    }
  ],
  "pagination": {
    "total": 214,
    "limit": 25,
    "offset": 0,
    "has_more": true
  }
}

POST /leads

Add leads

Add or update up to 2,000 leads without enrolling them anywhere — the same import as a CSV upload in the app: deduped by email across the workspace (an existing lead is updated and moved into the list), blocklisted addresses refused, and new leads added only up to the room the workspace has left (the leads its plan keeps; a full workspace adds none and says so in errors). Custom fields become {{variables}} for campaign copy. To enrol them as well, use POST /campaigns/{id}/leads.

Parameters

ParameterTypeInNotes
Idempotency-KeystringheaderOptional: a unique key (a UUID) so a retry of this request replays the first response instead of doing it twice. Kept for 24 hours.

Body

FieldTypeNotes
leadsarray of objectrequired
list_idstringoptionalInto this existing list.
list_namestringoptionalElse a new list with this name (default "Added via API").

Request

curl -s -X POST "https://mailfleet.co/api/v1/leads" \
  -H "Authorization: Bearer sk_live_your_key" \
  -H "Content-Type: application/json" \
  -d '{"leads":[{"email":"priya@acme.com","first_name":"Priya","last_name":"Raman","company":"Acme","title":"Head of Operations"}],"list_id":"lst_9a12c4"}'

Response

{
  "object": "import",
  "list_id": "lst_9a12c4",
  "list_name": "Added via API",
  "imported": 180,
  "updated": 14,
  "skipped": 4,
  "blocked": 2,
  "custom_fields": [
    "email_1_body"
  ],
  "errors": [
    {
      "row": 7,
      "email": "not-an-address",
      "reason": "That isn't a valid email address."
    }
  ]
}

GET /leads/{id}

Get a lead

The lead with its custom fields ({{variables}}), its list, and the campaigns it's in — status and step in each.

Parameters

ParameterTypeInNotes
idstringrequiredThe lead's id.

Request

curl -s "https://mailfleet.co/api/v1/leads/led_4b81e0" \
  -H "Authorization: Bearer sk_live_your_key"

Response

{
  "object": "lead",
  "id": "cmp_9f2a1c",
  "email": "priya@acme.com",
  "first_name": "Priya",
  "last_name": "Raman",
  "company": "Acme",
  "title": "Head of Operations",
  "city": "Leeds",
  "website": "acme.com",
  "phone": "…",
  "linkedin_url": "…",
  "status": "NEW",
  "created_at": "2026-09-22T09:14:02.000Z",
  "list_id": "lst_9a12c4",
  "timezone": "Europe/London",
  "custom": {
    "email_1_body": "Priya — congrats on the Leeds depot…"
  },
  "campaigns": [
    {
      "campaign_id": "cmp_9f2a1c",
      "name": "Acme — Q4 outbound",
      "campaign_status": "DRAFT",
      "status": "ACTIVE",
      "step": 128,
      "last_sent_at": "2026-09-22T09:14:02.000Z",
      "next_due_at": "2026-09-22T09:14:02.000Z"
    }
  ]
}

PATCH /leads/{id}

Update a lead

Any field — an empty string clears it — and custom fields: a key set to "" is removed, others are added or replaced, the rest kept. The same edit as the lead panel in the app. An address another lead already uses is refused (409 email_taken).

Parameters

ParameterTypeInNotes
idstringrequiredThe lead's id.

Body

FieldTypeNotes
emailstringoptional
first_namestringoptional
last_namestringoptional
companystringoptional
titlestringoptional
websitestringoptional
industrystringoptional
employee_countstringoptional
citystringoptional
countrystringoptional
phonestringoptional
linkedin_urlstringoptional
timezonestringoptionalIANA timezone (used when a campaign respects each lead's timezone).
customobjectoptionalKeys of letters, digits and underscores. "" removes a key; others are added or replaced.

Request

curl -s -X PATCH "https://mailfleet.co/api/v1/leads/led_4b81e0" \
  -H "Authorization: Bearer sk_live_your_key" \
  -H "Content-Type: application/json" \
  -d '{"company":"Acme Logistics","custom":{"email_1_body":"Priya — congrats on the Leeds depot…"}}'

Response

{
  "object": "lead",
  "id": "cmp_9f2a1c",
  "email": "priya@acme.com",
  "first_name": "Priya",
  "last_name": "Raman",
  "company": "Acme",
  "title": "Head of Operations",
  "city": "Leeds",
  "website": "acme.com",
  "phone": "…",
  "linkedin_url": "…",
  "status": "NEW",
  "created_at": "2026-09-22T09:14:02.000Z",
  "list_id": "lst_9a12c4",
  "timezone": "Europe/London",
  "custom": {
    "email_1_body": "Priya — congrats on the Leeds depot…"
  },
  "campaigns": [
    {
      "campaign_id": "cmp_9f2a1c",
      "name": "Acme — Q4 outbound",
      "campaign_status": "DRAFT",
      "status": "ACTIVE",
      "step": 128,
      "last_sent_at": "2026-09-22T09:14:02.000Z",
      "next_due_at": "2026-09-22T09:14:02.000Z"
    }
  ]
}

POST /leads/{id}/unsubscribe

Unsubscribe a lead

As if they clicked the unsubscribe link: marked Unsubscribed, stopped in every campaign still sending or paused, never enrolled again, and the lead.unsubscribed webhook fires. To block an address or a whole domain, use the blocklist.

Parameters

ParameterTypeInNotes
idstringrequiredThe lead's id.

Request

curl -s -X POST "https://mailfleet.co/api/v1/leads/led_4b81e0/unsubscribe" \
  -H "Authorization: Bearer sk_live_your_key"

Response

{
  "object": "lead",
  "id": "led_4b81e0",
  "email": "priya@acme.com",
  "status": "UNSUBSCRIBED",
  "campaigns_stopped": 1
}

GET /lists

List lead lists

Parameters

ParameterTypeInNotes
limitintegerqueryPage size, 1–100 (default 25). Default 25.
offsetintegerqueryNumber of items to skip (default 0). Default 0.

Request

curl -s "https://mailfleet.co/api/v1/lists" \
  -H "Authorization: Bearer sk_live_your_key"

Response

{
  "object": "list",
  "data": [
    {
      "object": "list",
      "id": "lst_9a12c4",
      "name": "Med spas — Texas",
      "leads": 412,
      "created_at": "2026-09-22T09:14:02.000Z"
    }
  ],
  "pagination": {
    "total": 214,
    "limit": 25,
    "offset": 0,
    "has_more": true
  }
}

POST /lists

Create a list

An empty list; add leads to it with POST /leads and its list_id.

Parameters

ParameterTypeInNotes
Idempotency-KeystringheaderOptional: a unique key (a UUID) so a retry of this request replays the first response instead of doing it twice. Kept for 24 hours.

Body

FieldTypeNotes
namestringrequired

Request

curl -s -X POST "https://mailfleet.co/api/v1/lists" \
  -H "Authorization: Bearer sk_live_your_key" \
  -H "Content-Type: application/json" \
  -d '{"name":"Med spas — Texas"}'

Response

{
  "object": "list",
  "id": "lst_9a12c4",
  "name": "Med spas — Texas",
  "leads": 412,
  "created_at": "2026-09-22T09:14:02.000Z"
}

GET /replies

List replies

Newest first. To sync your inbox, poll with received_after (the newest received_at you've seen) — or subscribe to the reply.received webhook instead.

Parameters

ParameterTypeInNotes
limitintegerqueryPage size, 1–100 (default 25). Default 25.
offsetintegerqueryNumber of items to skip (default 0). Default 0.
intentINTERESTED · MEETING · QUESTION · NOT_NOW · NEUTRAL · UNSUBSCRIBEqueryFilter by detected intent.
campaign_idstringqueryFilter by campaign.
lead_idstringqueryOne lead's replies.
handledbooleanquerytrue = handled only, false = still open.
received_afterstring (date-time)queryOnly replies received after this time (ISO 8601).

Request

curl -s "https://mailfleet.co/api/v1/replies?intent=INTERESTED" \
  -H "Authorization: Bearer sk_live_your_key"

Response

{
  "object": "list",
  "data": [
    {
      "object": "reply",
      "id": "rep_77c3a9",
      "from_email": "priya@acme.com",
      "from_name": "Priya Raman",
      "subject": "Re: Quick question about Acme",
      "preview": "Happy to take a look — how does Thursday suit?",
      "intent": "INTERESTED",
      "campaign_id": "cmp_9f2a1c",
      "lead_id": "led_4b81e0",
      "sender_id": "snd_2e10b4",
      "handled": false,
      "received_at": "2026-09-22T09:14:02.000Z"
    }
  ],
  "pagination": {
    "total": 214,
    "limit": 25,
    "offset": 0,
    "has_more": true
  }
}

GET /replies/{id}

Get a conversation

The whole thread: every campaign email the lead was sent (step, delivery, when they opened or clicked), their replies and yours, in order — plus how it's classified, whether it's handled, and teammates' notes. Each message's text is the reply without the quoted history.

Parameters

ParameterTypeInNotes
idstringrequiredThe reply's id.

Request

curl -s "https://mailfleet.co/api/v1/replies/rep_77c3a9" \
  -H "Authorization: Bearer sk_live_your_key"

Response

{
  "object": "conversation",
  "id": "rep_77c3a9",
  "subject": "Re: Quick question about Acme",
  "class": "Interested",
  "intent": "INTERESTED",
  "tags": [
    "hot"
  ],
  "handled": false,
  "read": true,
  "starred": false,
  "snoozed_until": null,
  "received_at": "2026-09-22T09:14:02.000Z",
  "sender": "sam@outbound.acme.com",
  "lead": {
    "id": "cmp_9f2a1c",
    "email": "priya@acme.com",
    "name": "Priya Raman",
    "company": "Acme",
    "title": "Head of Operations",
    "phone": "…",
    "status": "REPLIED"
  },
  "campaign": {
    "id": "cmp_9f2a1c",
    "name": "Acme — Q4 outbound",
    "status": "…"
  },
  "messages": [
    {
      "direction": "in",
      "kind": "reply_received",
      "from": "priya@acme.com",
      "to": "sam@outbound.acme.com",
      "subject": "Re: Quick question about Acme",
      "text": "Happy to take a look — how does Thursday suit?",
      "text_truncated": true,
      "at": "2026-09-22T09:14:02.000Z",
      "step": 128,
      "campaign": "…",
      "delivery": "…",
      "opened_at": "2026-09-22T09:14:02.000Z",
      "clicked_at": "2026-09-22T09:14:02.000Z"
    }
  ],
  "notes": [
    {
      "kind": "note",
      "by": "…",
      "at": "2026-09-22T09:14:02.000Z",
      "body": "…",
      "due_at": "2026-09-22T09:14:02.000Z",
      "done": true
    }
  ],
  "not_shown": "…"
}

PATCH /replies/{id}

Mark a conversation

As the Replies inbox does. mark is "Mark lead as": Interested, Meeting request, Question, Not now, Not interested, OOO, Wrong person or Referral classify the thread (and start any subsequence waiting on that tag); Meeting booked and Won also move the lead's status (Won marks it handled); Unsubscribed stops the lead in every campaign. handled and read cover the whole conversation; tags are your own labels (the classification is set with mark).

Parameters

ParameterTypeInNotes
idstringrequiredThe reply's id.

Body

FieldTypeNotes
markInterested · Meeting request · Question · Not now · Not interested · OOO · Wrong person · Referral · Meeting booked · Won · Unsubscribedoptional"Mark lead as" — the classification and what goes with it.
handledbooleanoptionalResolve (or reopen) the whole conversation.
readbooleanoptional
tagsarray of stringoptionalYour own labels (replaces them).
starredbooleanoptional
snoozed_untilstring (date-time), nullableoptionalOut of the inbox until then (within a year); null wakes it.

Request

curl -s -X PATCH "https://mailfleet.co/api/v1/replies/rep_77c3a9" \
  -H "Authorization: Bearer sk_live_your_key" \
  -H "Content-Type: application/json" \
  -d '{"mark":"Meeting booked","handled":true}'

Response

{
  "object": "conversation",
  "id": "rep_77c3a9",
  "subject": "Re: Quick question about Acme",
  "class": "Interested",
  "intent": "INTERESTED",
  "tags": [
    "hot"
  ],
  "handled": false,
  "read": true,
  "starred": false,
  "snoozed_until": null,
  "received_at": "2026-09-22T09:14:02.000Z",
  "sender": "sam@outbound.acme.com",
  "lead": {
    "id": "cmp_9f2a1c",
    "email": "priya@acme.com",
    "name": "Priya Raman",
    "company": "Acme",
    "title": "Head of Operations",
    "phone": "…",
    "status": "REPLIED"
  },
  "campaign": {
    "id": "cmp_9f2a1c",
    "name": "Acme — Q4 outbound",
    "status": "…"
  },
  "messages": [
    {
      "direction": "in",
      "kind": "reply_received",
      "from": "priya@acme.com",
      "to": "sam@outbound.acme.com",
      "subject": "Re: Quick question about Acme",
      "text": "Happy to take a look — how does Thursday suit?",
      "text_truncated": true,
      "at": "2026-09-22T09:14:02.000Z",
      "step": 128,
      "campaign": "…",
      "delivery": "…",
      "opened_at": "2026-09-22T09:14:02.000Z",
      "clicked_at": "2026-09-22T09:14:02.000Z"
    }
  ],
  "notes": [
    {
      "kind": "note",
      "by": "…",
      "at": "2026-09-22T09:14:02.000Z",
      "body": "…",
      "due_at": "2026-09-22T09:14:02.000Z",
      "done": true
    }
  ],
  "not_shown": "…"
}

POST /replies/{id}/reply

Reply to a lead

A real email, sent from the mailbox their message arrived on, in the same thread ("Re:" the subject) — exactly as replying in the Replies inbox: plain text (blank lines make paragraphs), the mailbox's CRM copy if it has one, a slot of its daily limit, and it goes out even while that mailbox is paused.

The same text can't go to the same conversation twice within a minute (409 duplicate_reply); send an Idempotency-Key to retry safely. It doesn't mark the conversation handled — PATCH it for that.

Parameters

ParameterTypeInNotes
idstringrequiredThe reply's id.
Idempotency-KeystringheaderOptional: a unique key (a UUID) so a retry of this request replays the first response instead of doing it twice. Kept for 24 hours.

Body

FieldTypeNotes
bodystringrequiredThe reply's text.

Request

curl -s -X POST "https://mailfleet.co/api/v1/replies/rep_77c3a9/reply" \
  -H "Authorization: Bearer sk_live_your_key" \
  -H "Content-Type: application/json" \
  -d '{"body":"Thanks Priya — Thursday at 2pm works. I'll send an invite."}'

Response

{
  "object": "message",
  "direction": "out",
  "reply_id": "rep_77c3a9",
  "to": "priya@acme.com",
  "subject": "Re: Quick question about Acme",
  "sender_id": "snd_2e10b4",
  "sent_at": "2026-09-22T09:14:02.000Z"
}

GET /senders

List senders (mailboxes)

Parameters

ParameterTypeInNotes
limitintegerqueryPage size, 1–100 (default 25). Default 25.
offsetintegerqueryNumber of items to skip (default 0). Default 0.
statusACTIVE · UNVERIFIED · ERROR · PAUSEDqueryFilter by status.

Request

curl -s "https://mailfleet.co/api/v1/senders?status=ACTIVE" \
  -H "Authorization: Bearer sk_live_your_key"

Response

{
  "object": "list",
  "data": [
    {
      "object": "sender",
      "id": "snd_2e10b4",
      "email": "sam@outbound.acme.com",
      "name": "Sam Reed",
      "domain": "outbound.acme.com",
      "provider": "google",
      "status": "ACTIVE",
      "health": 92,
      "daily_limit": 40,
      "sent_today": 22,
      "gap_minutes": 20,
      "warmup_enabled": true,
      "has_imap": true,
      "last_error": null,
      "created_at": "2026-07-19T11:23:04.000Z"
    }
  ],
  "pagination": {
    "total": 214,
    "limit": 25,
    "offset": 0,
    "has_more": true
  }
}

GET /stats

Workspace summary stats

Parameters

No parameters.

Request

curl -s "https://mailfleet.co/api/v1/stats" \
  -H "Authorization: Bearer sk_live_your_key"

Response

{
  "object": "stats",
  "campaigns": {
    "total": 9,
    "sending": 3
  },
  "senders": {
    "total": 14,
    "active": 11
  },
  "leads": {
    "total": 2400
  },
  "replies": {
    "total": 74,
    "unhandled": 9
  },
  "emails": {
    "sent_all_time": 41908,
    "sent_today": 388,
    "delivered_all_time": 128
  },
  "engagement": {
    "replied": 74,
    "interested": 21,
    "bounced": 18,
    "hard_bounced": 128,
    "blocked": 128,
    "leads_reached": 128,
    "auto_replied": 128
  }
}

GET /blocklist

List the blocklist

Addresses and whole domains that are never contacted, newest first.

Parameters

ParameterTypeInNotes
limitintegerqueryPage size, 1–100 (default 25). Default 25.
offsetintegerqueryNumber of items to skip (default 0). Default 0.
kindemail · domainqueryOnly addresses or only domains.

Request

curl -s "https://mailfleet.co/api/v1/blocklist?kind=domain" \
  -H "Authorization: Bearer sk_live_your_key"

Response

{
  "object": "list",
  "data": [
    {
      "object": "blocklist_entry",
      "id": "cmp_9f2a1c",
      "value": "competitor.com",
      "kind": "domain",
      "note": "api",
      "created_at": "2026-09-22T09:14:02.000Z"
    }
  ],
  "pagination": {
    "total": 214,
    "limit": 25,
    "offset": 0,
    "has_more": true
  }
}

POST /blocklist

Block addresses or domains

As Settings → Blocklist does: leads already in the workspace that match are marked Blocked and stopped in every campaign, and a blocked address is never imported or enrolled again. A URL counts as its domain. Up to 20,000 entries per call.

Parameters

ParameterTypeInNotes
Idempotency-KeystringheaderOptional: a unique key (a UUID) so a retry of this request replays the first response instead of doing it twice. Kept for 24 hours.

Body

FieldTypeNotes
entriesarray of stringrequired
notestringoptionalWhere they came from (shown in Settings).

Request

curl -s -X POST "https://mailfleet.co/api/v1/blocklist" \
  -H "Authorization: Bearer sk_live_your_key" \
  -H "Content-Type: application/json" \
  -d '{"entries":["jo@competitor.com","competitor.com"]}'

Response

{
  "object": "blocklist_result",
  "added": 2,
  "already_blocked": 0,
  "invalid": [],
  "leads_blocked": 1
}

GET /webhooks

List webhooks

Your endpoints and the events each gets. Signing secrets aren't listed — each is shown once, when it's created.

Parameters

No parameters.

Request

curl -s "https://mailfleet.co/api/v1/webhooks" \
  -H "Authorization: Bearer sk_live_your_key"

Response

{
  "object": "list",
  "data": [
    {
      "object": "webhook",
      "id": "whk_1d4f07",
      "url": "https://your-app.com/hooks/mailfleet",
      "events": [
        "reply.received",
        "reply.interested"
      ],
      "active": true,
      "last_status": 200,
      "last_fired_at": "2026-09-22T09:14:02.000Z",
      "created_at": "2026-09-22T09:14:02.000Z",
      "secret": "whsec_…"
    }
  ],
  "pagination": {
    "total": 214,
    "limit": 25,
    "offset": 0,
    "has_more": true
  }
}

POST /webhooks

Create a webhook

A signed POST to your URL the moment each chosen event happens (X-MailFleet-Signature: HMAC-SHA256 of the raw body with the secret), retried for 24 hours if your endpoint is down. The secret is in this response only — keep it. Up to 25 webhooks per workspace.

Parameters

ParameterTypeInNotes
Idempotency-KeystringheaderOptional: a unique key (a UUID) so a retry of this request replays the first response instead of doing it twice. Kept for 24 hours.

Body

FieldTypeNotes
urlstring (uri)required
eventsarray of email.sent · email.opened · link.clicked · reply.received · reply.interested · reply.tagged · email.bounced · lead.unsubscribed · lead.enriched · deal.created · deal.stage_changed · deal.won · deal.lostrequired

Request

curl -s -X POST "https://mailfleet.co/api/v1/webhooks" \
  -H "Authorization: Bearer sk_live_your_key" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://your-app.com/hooks/mailfleet","events":["reply.received","reply.interested"]}'

Response

{
  "object": "webhook",
  "id": "whk_1d4f07",
  "url": "https://your-app.com/hooks/mailfleet",
  "events": [
    "reply.received",
    "reply.interested"
  ],
  "active": true,
  "last_status": 200,
  "last_fired_at": "2026-09-22T09:14:02.000Z",
  "created_at": "2026-09-22T09:14:02.000Z",
  "secret": "whsec_…"
}

DELETE /webhooks/{id}

Delete a webhook

Parameters

ParameterTypeInNotes
idstringrequiredThe webhook's id.

Request

curl -s -X DELETE "https://mailfleet.co/api/v1/webhooks/whk_1d4f07" \
  -H "Authorization: Bearer sk_live_your_key"

Response

{
  "object": "campaign",
  "id": "cmp_9f2a1c",
  "deleted": true
}

Search the Lead Database

Search 53M people by title, seniority, location, industry and company size. Free — only a reveal costs anything.

Addresses come back masked. /leaddb/reveal is the only path to a full address. Array filters repeat the key: ?seniority=vp&seniority=head.

pagination.capped is true when more than 10,000 people match; total is then a floor, not a count. Rate limit: 60 requests/minute.

Parameters

ParameterTypeInNotes
limitintegerqueryPage size, 1–100 (default 25). Default 25.
offsetintegerqueryNumber of items to skip (default 0). Default 0.
qstringqueryFree text across name, title and company.
titlestringquerySingle job-title match.
titlesarray of stringqueryAny-of job titles (repeat the key, or comma-separate).
exclude_titlesarray of stringqueryNone-of job titles.
similar_titlesbooleanqueryExpand each title to its common variants.
seniorityarray of c_suite · founder · owner · partner · vp · head · director · manager · senior · entry · internqueryAny-of seniority bands.
countriesarray of stringqueryAny-of countries.
statesarray of stringqueryAny-of states or regions.
industriesarray of stringqueryAny-of industries.
employeesarray of stringqueryAny-of company-size bands.
emailany · has · verifiedqueryRequire an address, or a verified one.
phonebooleanqueryOnly people with a phone number on file.
savedall · saved · newqueryAll people, only ones you have already acquired, or only new ones.

Request

curl -s "https://mailfleet.co/api/v1/leaddb/search?q=Priya%20Raman" \
  -H "Authorization: Bearer sk_live_your_key"

Response

{
  "object": "list",
  "data": [
    {
      "object": "leaddb_person",
      "id": "48210337",
      "full_name": "Priya Raman",
      "first_name": "Priya",
      "last_name": "Raman",
      "title": "Head of Operations",
      "seniority": "head",
      "email_masked": "p****@acme.com",
      "email_status": "verified",
      "email_corporate": true,
      "email_check": "valid",
      "email_checked_at": "2026-09-19T04:11:20.000Z",
      "has_phone": true,
      "linkedin_url": "https://www.linkedin.com/in/example-lead",
      "city": "Leeds",
      "state": "West Yorkshire",
      "country": "United Kingdom",
      "company": {
        "name": "Acme",
        "domain": "acme.com",
        "industry": "Logistics",
        "employees": 120
      },
      "acquired": false
    }
  ],
  "pagination": {
    "total": 214,
    "limit": 25,
    "offset": 0,
    "has_more": true,
    "capped": false,
    "count_cap": 10000
  }
}

POST /leaddb/reveal

Reveal a person's email address

The only endpoint that returns an unmasked address, and the only one that spends anything.

You pay once per person, ever. Revealing someone you have already acquired returns charged: false and costs nothing. A charged reveal spends one unit of the monthly Lead Database allowance (and one lead credit on a workspace still on monthly lead credits). Nothing is charged unless an address is handed over.

A reveal saves the person to your leads. The lead takes one place in your workspace like any other, in a list named "Revealed from Lead Database" (or the add_to_list list), with the same fields an add brings; deleting it frees the place, and revealing them again later is free of the allowance and saves them again. So a reveal needs one free place, unless the person is already one of your leads (matched by email): with none, it answers 402 lead_storage_full and charges nothing. saved_to_leads, lead_list and lead_id say where they are. If the address was handed over but the lead couldn't be written (or the address would bounce, or is blocklisted), saved_to_leads is false and save_note says why. A workspace still on monthly lead credits keeps nothing from a reveal, as before — use add_to_list.

The address is verified live at reveal time (a verdict under 24 hours old is reused). One person per call. Rate limit: 60 requests/minute.

Parameters

No parameters.

Body

FieldTypeNotes
person_idstringrequiredThe id of a person from /leaddb/search.
add_to_liststringoptionalThe list the person is saved into as a lead — the id of one of your lead lists, or new for a fresh one — instead of "Revealed from Lead Database". On a workspace still on monthly lead credits, which keeps nothing from a reveal, this is what adds them. Free: the import skips anyone already acquired.

Request

curl -s -X POST "https://mailfleet.co/api/v1/leaddb/reveal" \
  -H "Authorization: Bearer sk_live_your_key" \
  -H "Content-Type: application/json" \
  -d '{"person_id":"48210337"}'

Response

{
  "object": "leaddb_reveal",
  "person_id": "48210337",
  "email": "priya.raman@acme.com",
  "verification": {
    "outcome": "valid",
    "code": null,
    "checked_at": "2026-09-22T09:14:02.000Z",
    "cached": false
  },
  "charged": true,
  "lead_id": "led_4b81e0",
  "saved_to_leads": true,
  "lead_list": {
    "id": "cmp_9f2a1c",
    "name": "Revealed from Lead Database"
  },
  "save_note": "…",
  "quota": {
    "used": 1841,
    "cap": 5000,
    "remaining": 3159,
    "period": "2026-09"
  }
}

POST /leads/{id}/enrich

Find a lead's mobile number

Reveal the lead's own mobile or direct line through your Apollo key (Settings → Integrations). The company switchboard is never substituted.

Asynchronous. Apollo sends the number back a little later, so this answers 202 pending. Subscribe to the lead.enriched webhook rather than polling — it fires the moment the reveal settles, found or not.

Spends no MailFleet credit; Apollo bills your own key. Limits: 30/minute and 500/day per workspace, so a runaway script cannot drain your Apollo balance.

Parameters

ParameterTypeInNotes
idstringrequired

Request

curl -s -X POST "https://mailfleet.co/api/v1/leads/led_4b81e0/enrich" \
  -H "Authorization: Bearer sk_live_your_key"

Response

{
  "object": "enrichment",
  "id": "enr_71c0aa",
  "lead_id": "led_4b81e0",
  "status": "pending"
}

GET /leads/{id}/enrich

Check an enrichment

The lead's current number and the state of its most recent enrichment. status is null when the lead has never been enriched.

Parameters

ParameterTypeInNotes
idstringrequired

Request

curl -s "https://mailfleet.co/api/v1/leads/led_4b81e0/enrich" \
  -H "Authorization: Bearer sk_live_your_key"

Response

{
  "object": "enrichment",
  "id": "enr_71c0aa",
  "lead_id": "led_4b81e0",
  "status": "done",
  "phone": "+44 7700 900123",
  "updated_at": "2026-09-22T09:16:41.000Z"
}

Objects

Campaign

FieldTypeNotes
objectstring
idstring
namestring
statusDRAFT · SENDING · PAUSED · FINISHED
statsobject
leads sent opened clicked replied interested bounced skipped delivered hard_bounced blocked leads_reached auto_replied
Counted from the campaign's emails and replies. sent, delivered, bounced, hard_bounced and blocked count emails; opened, clicked, replied and interested count people, out of leads_reached. Every recorded open counts, including the automatic ones mail-security scanners make on delivery; clicks in the first 5 minutes after sending are left out (scanners follow every link on delivery), and so are out-of-office and auto-replies.
started_atstring (date-time), nullable
created_atstring (date-time)

Sender

FieldTypeNotes
objectstring
idstring
emailstring
namestring
domainstring
providerstring
statusACTIVE · UNVERIFIED · ERROR · PAUSED
healthinteger0–100 reputation score.
daily_limitinteger
sent_todayinteger
gap_minutesinteger
warmup_enabledboolean
has_imapboolean
last_errorstring, nullable
created_atstring (date-time)

Lead

FieldTypeNotes
objectstring
idstring
emailstring
first_namestring, nullable
last_namestring, nullable
companystring, nullable
titlestring, nullable
citystring, nullable
websitestring, nullable
phonestring, nullableFilled in by /leads/{id}/enrich, or from your import.
linkedin_urlstring, nullable
statusNEW · CONTACTED · REPLIED · MEETING · WON · BOUNCED · UNSUBSCRIBED · BLOCKED
created_atstring (date-time)

Reply

FieldTypeNotes
objectstring
idstring
from_emailstring
from_namestring, nullable
subjectstring, nullable
previewstring, nullable
intentINTERESTED · MEETING · QUESTION · NOT_NOW · NEUTRAL · UNSUBSCRIBE
campaign_idstring, nullable
lead_idstring, nullable
sender_idstring, nullable
handledboolean
received_atstring (date-time)

CampaignDetail

FieldTypeNotes
objectstring
idstring
namestring
statusDRAFT · SENDING · PAUSED · FINISHED
statsobject
leads sent opened clicked replied interested bounced skipped delivered hard_bounced blocked leads_reached auto_replied
Counted from the campaign's emails and replies. sent, delivered, bounced, hard_bounced and blocked count emails; opened, clicked, replied and interested count people, out of leads_reached. Every recorded open counts, including the automatic ones mail-security scanners make on delivery; clicks in the first 5 minutes after sending are left out (scanners follow every link on delivery), and so are out-of-office and auto-replies.
started_atstring (date-time), nullable
created_atstring (date-time)
typeemail · multichannel · linkedin · subsequenceemail campaigns are the ones the API writes sequences for.
step_countintegerSteps in the sequence.
sender_idsarray of stringThe mailboxes it sends from. Empty = every healthy sender.
scheduleobject
object timezone days window_from window_to gap_minutes new_leads_per_day respect_lead_timezone start_date

Schedule

FieldTypeNotes
objectstring
timezonestringIANA timezone the window is read in.
daysarray of Mon · Tue · Wed · Thu · Fri · Sat · SunDays it sends on.
window_fromstringStart of the sending window, 24-hour HH:MM.
window_tostringEnd of the sending window.
gap_minutesintegerMinutes between two emails from the same mailbox (1–240).
new_leads_per_dayintegerFirst emails a day, across the campaign.
respect_lead_timezonebooleanRead the window in each lead's own timezone (their timezone field), else the campaign's.
start_datestring, nullableYYYY-MM-DD; null = start straight away.

ScheduleInput

FieldTypeNotes
timezonestring
daysarray of Mon · Tue · Wed · Thu · Fri · Sat · Sun
window_fromstring
window_tostring
gap_minutesinteger
new_leads_per_dayinteger
respect_lead_timezoneboolean
start_datestringYYYY-MM-DD, or "" to start straight away.

Step

FieldTypeNotes
objectstring
numberintegerPosition in the sequence, from 1.
typeemail · manual · callMulti Channel sequences also have linkedin_* and condition steps.
wait_daysintegerDays after the previous step (0 for the first).
subjectstringEmail subject, or the call objective.
bodystringEmail body or call script.
variantsarray of objectA/B variants (up to 4).
due_daysinteger, nullablemanual/call: days the person has to do the task.
notestringmanual/call: an internal note shown with the task.

StepInput

FieldTypeNotes
typeemail · manual · callDefault email. call needs the calling add-on.
subjectstringEmail subject, or the call objective.
bodystringRequired for email and manual steps.
wait_daysintegerDays after the previous step (default 3; always 0 for the first).
due_daysintegermanual/call: days to do the task (default 2).
notestringmanual/call: an internal note.
variantsarray of object

Variant

FieldTypeNotes
subjectstring
bodystring

CampaignSender

FieldTypeNotes
objectstring
idstring
emailstring
namestring
statusACTIVE · UNVERIFIED · ERROR · PAUSED

CampaignLead

FieldTypeNotes
objectstring
statusACTIVE · PAUSED · REPLIED · BOUNCED · UNSUBSCRIBED · FINISHED
stepintegerThe step they're on, from 1 (past the last step once finished).
waiting_on_taskbooleanParked on a manual or call task until someone completes it.
last_sent_atstring (date-time), nullable
next_due_atstring (date-time), nullableWhen their next step is due.
enrolled_atstring (date-time)
leadobject
object id email first_name last_name company title city website phone linkedin_url status created_at

CampaignLeadRemoved

FieldTypeNotes
objectstring
campaign_idstring
lead_idstring
removedboolean
endedbooleanA Multi Channel or LinkedIn enrolment is ended rather than deleted.
campaign_leadsintegerPeople left in the campaign.

LeadInput

FieldTypeNotes
emailstring
first_namestring
last_namestring
companystring
titlestring
citystring
countrystring
websitestring
industrystring
phonestring
linkedin_urlstring
customobjectExtra fields, each a {{variable}} in the copy ("Email 1 Body" becomes email_1_body).

LeadEdit

FieldTypeNotes
emailstring
first_namestring
last_namestring
companystring
titlestring
websitestring
industrystring
employee_countstring
citystring
countrystring
phonestring
linkedin_urlstring
timezonestringIANA timezone (used when a campaign respects each lead's timezone).
customobjectKeys of letters, digits and underscores. "" removes a key; others are added or replaced.

LeadDetail

FieldTypeNotes
objectstring
idstring
emailstring
first_namestring, nullable
last_namestring, nullable
companystring, nullable
titlestring, nullable
citystring, nullable
websitestring, nullable
phonestring, nullableFilled in by /leads/{id}/enrich, or from your import.
linkedin_urlstring, nullable
statusNEW · CONTACTED · REPLIED · MEETING · WON · BOUNCED · UNSUBSCRIBED · BLOCKED
created_atstring (date-time)
list_idstring, nullable
timezonestring, nullable
customobjectThe lead's custom fields.
campaignsarray of objectThe campaigns it's in (the 50 most recent).

LeadUnsubscribed

FieldTypeNotes
objectstring
idstring
emailstring
statusstring
campaigns_stoppedinteger

Import

FieldTypeNotes
objectstring
list_idstring
list_namestring
importedintegerNew leads.
updatedintegerLeads already in the workspace, updated and moved into the list.
skippedintegerRows without a usable address, or past the room the workspace has for new leads.
blockedintegerOn the blocklist — not added.
custom_fieldsarray of string
errorsarray of object

Enrollment

FieldTypeNotes
objectstring
campaign_idstring
list_idstring
importedinteger
updatedinteger
skippedinteger
blockedinteger
enrolledintegerPeople added to the campaign now.
in_listinteger, nullableWhen enrolling a whole list: its size.
skipped_active_elsewhereinteger
campaign_statusDRAFT · SENDING · PAUSED · FINISHED
errorsarray of object

RowError

FieldTypeNotes
rowinteger
emailstring
reasonstring

List

FieldTypeNotes
objectstring
idstring
namestring
leadsinteger
created_atstring (date-time)

Conversation

FieldTypeNotes
objectstring
idstring
subjectstring, nullable
classstringHow the inbox classifies it: a mark (Interested, Meeting request …), or Reply.
intentINTERESTED · MEETING · QUESTION · NOT_NOW · NEUTRAL · UNSUBSCRIBE
tagsarray of stringYour own labels.
handledboolean
readboolean
starredboolean
snoozed_untilstring (date-time), nullable
received_atstring (date-time)
senderstring, nullableThe mailbox it arrived on.
leadobject
id email name company title phone status
campaignobject, nullable
id name status
messagesarray of object
notesarray of object
not_shownstringPresent when a very long history was cut: what was left out.

Message

FieldTypeNotes
directionout · in
kindcampaign_email · reply_received · reply_sent
fromstring, nullable
tostring, nullable
subjectstring, nullable
textstringWithout the quoted history; up to 4,000 characters.
text_truncatedbooleanPresent (true) when the text was cut.
atstring (date-time)
stepinteger, nullablecampaign_email: which step it was.
campaignstring, nullablecampaign_email: the campaign's name.
deliverystring, nullablecampaign_email: delivered, bounced or blocked.
opened_atstring (date-time), nullablecampaign_email: first recorded open.
clicked_atstring (date-time), nullablecampaign_email: first click by a person.

ReplyPatch

FieldTypeNotes
markInterested · Meeting request · Question · Not now · Not interested · OOO · Wrong person · Referral · Meeting booked · Won · Unsubscribed"Mark lead as" — the classification and what goes with it.
handledbooleanResolve (or reopen) the whole conversation.
readboolean
tagsarray of stringYour own labels (replaces them).
starredboolean
snoozed_untilstring (date-time), nullableOut of the inbox until then (within a year); null wakes it.

SentMessage

FieldTypeNotes
objectstring
directionstring
reply_idstring
tostring
subjectstring
sender_idstring
sent_atstring (date-time)

BlocklistEntry

FieldTypeNotes
objectstring
idstring
valuestring
kindemail · domain
notestring, nullable
created_atstring (date-time)

BlocklistResult

FieldTypeNotes
objectstring
addedinteger
already_blockedinteger
invalidarray of stringEntries that aren't an address or a domain.
leads_blockedintegerExisting leads now Blocked and stopped.

Webhook

FieldTypeNotes
objectstring
idstring
urlstring
eventsarray of email.sent · email.opened · link.clicked · reply.received · reply.interested · reply.tagged · email.bounced · lead.unsubscribed · lead.enriched · deal.created · deal.stage_changed · deal.won · deal.lost
activeboolean
last_statusinteger, nullable
last_fired_atstring (date-time), nullable
created_atstring (date-time)
secretstringOnly when it's created — keep it to verify signatures.

Deleted

FieldTypeNotes
objectstring
idstring
deletedboolean

LeadDbPerson

FieldTypeNotes
objectstring
idstringLead Database person id — the handle for /leaddb/reveal.
full_namestring
first_namestring, nullable
last_namestring, nullable
titlestring, nullable
senioritystring, nullable
email_maskedstring, nullableMasked. Reveal the address with /leaddb/reveal.
email_statusstring, nullableWhat the source data claims: verified, extrapolated or unavailable.
email_corporateboolean
email_checkstring, nullableOur verifier's own last verdict — stronger than email_status. Null = never probed.
email_checked_atstring (date-time), nullable
has_phoneboolean
linkedin_urlstring, nullable
citystring, nullable
statestring, nullable
countrystring, nullable
companyobject
name domain industry employees
acquiredbooleanYou have already paid for this person — revealing them again is free.

LeadDbPagination

FieldTypeNotes
totalinteger
limitinteger
offsetinteger
has_moreboolean
cappedbooleanMore than count_cap people match; total is a floor, not a count.
count_capinteger

LeadDbReveal

FieldTypeNotes
objectstring
person_idstring
emailstring
verificationobject
outcome code checked_at cached
chargedbooleanFalse when you had already paid for this person.
lead_idstring, nullableThe lead this person is in your workspace — saved by the reveal, already there, or added by add_to_list. Null when they aren't one of your leads.
saved_to_leadsbooleanThe person is one of your leads after this call: saved by the reveal (it takes one place in your workspace), or already one. False on a workspace still on monthly lead credits unless add_to_list was used, and when the save didn't happen — see save_note.
lead_listobject, nullable
id name
The list the lead is in — "Revealed from Lead Database" unless add_to_list named another. Null when not a lead, or in no list.
save_notestring, nullableWhy the address was handed over but the person wasn't saved to your leads (the address would bounce, it's blocklisted, the workspace filled up meanwhile, or a fault of ours), in a plain sentence. Null otherwise.
quotaobject
used cap remaining period
Your Lead Database allowance after this call. Null caps mean unlimited.

EnrichmentPending

FieldTypeNotes
objectstring
idstring
lead_idstring
statuspending

Enrichment

FieldTypeNotes
objectstring
idstring, nullable
lead_idstring
statuspending · done · not_found · failed
phonestring, nullableThe person's mobile or direct line. Never the company switchboard.
updated_atstring (date-time), nullable

Stats

FieldTypeNotes
objectstring
campaignsobject
total sending
sendersobject
total active
leadsobject
total
repliesobject
total unhandled
emailsobject
sent_all_time sent_today delivered_all_time
engagementobject
replied interested bounced hard_bounced blocked leads_reached auto_replied
Added up across campaigns, counted as each campaign's stats are: people who replied (out-of-office and auto-replies left out) and were interested; emails that bounced.

Errors

Errors are JSON with the same shape throughout: { "error": { "code": "invalid_request", "message": "Each lead needs a valid email address.", "param": "leads.3.email" } }. The message is plain words, safe to show a person; param names the field that failed.

StatusWhat it means
400The body or a parameter isn't valid — error.param names the field. A campaign that can't start yet lists what's missing in error.blockers.
401Missing, invalid or revoked API key.
403read_only_key: a read-only key tried to make a change. plan_required: the API is a Pro and Scale feature.
404No such resource in your workspace.
409It can't happen right now: a campaign that isn't sending, the same reply within a minute (duplicate_reply), an address another lead uses (email_taken), or a request with the same Idempotency-Key still running.
413The request body is over 5 MB.
422The Idempotency-Key was already used for a different request.
429More than 120 requests in a minute from one key.
502The mail server refused a reply; the message says why in plain words.

Run your outreach from your own tools

Every plan includes webhooks; Pro and Scale add the two-way REST API.