On this page

GET/campaigns/:campaignId/leads

Credits: 0
LinkedIn risk: None
Rate limit: Global only


Purpose

Paginated list of leads in a campaign. Essential for MCP and polling when webhooks are not enough. Responses are an allowlisted CRM shape only — never a raw lead dump.


Request

GET /campaigns/search_abc/leads?tag=Interested&sort=taggedAt&limit=50
X-API-Key: sk_live_...
QueryNotes
tagOptional exact tag filter (see PATCH …/leads/:leadId)
repliedOptional boolean
needsReplyOptional — leads waiting on a reply
excludedOptional — only true is supported (lists excluded leads). Any other value (including false) → 400 validation_error. Cleared leads are omitted from this filter.
sortOptional; always desc. Default createdAt. See matrix below
limitOptional; default 50, max 100
cursorOpaque pagination cursor from previous response. Unknown, deleted, malformed, missing the active sort field, or not matching the active tag / replied / needsReply / excluded filter → 400 validation_error (does not silently restart at page 1)

Filter rule: use at most one of tag, replied, needsReply, excluded per request. Combining them returns 400 validation_error.

sort matrix

sortAllowed with
createdAt (default)any / none
taggedAtrequires tag=…
repliedAtrequires replied=true|false
lastInboundMessageAtnone, or replied=true|false (not with tag)

needsReply and excluded=true only support sort=createdAt. Invalid combo → 400 with fields: ['sort'].

Missing fields: leads missing the active sort field are omitted from that sort. Old leads without taggedAt / repliedAt / lastInboundMessageAt will not appear in those sorts. Prefer GET /activity?type=reply_received for “last reply.” createdAt is a sort key only — it is not returned on each lead object.

Changing sort or filters mid-pagination requires a fresh cursor.


Response 200 (illustrative)

{
  "leads": [
    {
      "id": "lead_xyz",
      "fullName": "Jane Doe",
      "firstName": "Jane",
      "lastName": "Doe",
      "linkedinUrl": "https://www.linkedin.com/in/jane-doe/",
      "jobTitle": "VP Sales",
      "location": "London, UK",
      "companyName": "Acme",
      "companyLinkedin": "https://www.linkedin.com/company/acme/",
      "website": "https://acme.com",
      "email": "jane@acme.com",
      "tag": "Interested",
      "replied": true,
      "needsReply": true,
      "excluded": false,
      "stage": "replied",
      "repliedAt": "2026-03-01T12:00:00.000Z",
      "lastInboundMessageAt": "2026-03-02T15:30:00.000Z",
      "taggedAt": "2026-03-02T15:31:00.000Z"
    }
  ],
  "nextCursor": null
}

Name and contact fields

FieldNotes
fullNameFalls back to firstName + lastName when the stored value is empty — file imports often map only the separate name columns. null when there is nothing to compose from
firstName, lastNameStored name parts; null when unset
locationAlso present on get-lead
companyLinkedin, website, emailThe customer's own lead data, populated by file import or enrichment. Search-sourced leads are created with these blank, so expect null there
excludedtrue when the lead is excluded from outreach; otherwise false

Timestamps (ISO or null)

FieldNotes
repliedAtUpdated when the lead is marked replied (may move on later inbound writes — not a frozen “first reply only” stamp)
lastInboundMessageAtBest lead-level signal for recent inbound content when present
taggedAtWhen the current tag was set; null on historical/seed rows

For “when was my last reply?” prefer activity type=reply_received.

All string fields are trimmed and normalise empty to null, never "".

A lead's email is returned because it is the customer's own CRM data; this is distinct from an account owner's email, which is never exposed. Treat the response as personal data for GDPR purposes.

Prefer a small public stage enum:

new · connection_sent · connected · messaged · replied · tagged · excluded · failed · handed_off

Allowlisted fields only — never drafts, message logs, or provider ids. See get-lead for the slightly richer single-lead DTO.


Errors

error.codeWhen
unauthorizedMissing/invalid API key
not_foundUnknown campaign
validation_errorBad filter combo, excluded≠true, sort, or cursor
rate_limitedGlobal RPM exceeded

Good for

  • “Show me Interested leads”
  • “Show excluded leads” → excluded=true
  • MCP list_leads

MCP

Tool: list_leads (same single-filter + sort rules). Pass excluded: true only — never false.

See also