Skip to main content
GET
/
api
/
campaigns
/
{id}
/
leads
Get Leads in Campaign
curl --request GET \
  --url https://app.puffle.ai/api/campaigns/{id}/leads \
  --header 'Authorization: Bearer <token>'
{
  "prospects": [
    {
      "id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
      "campaign_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
      "user_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
      "status": "<string>",
      "email": "jsmith@example.com",
      "linkedin_url": "<string>",
      "first_name": "<string>",
      "last_name": "<string>",
      "name": "<string>",
      "company": "<string>",
      "position": "<string>",
      "headline": "<string>",
      "custom_fields": {},
      "source": "<string>",
      "source_ref": "<string>",
      "created_at": "2023-11-07T05:31:56Z",
      "updated_at": "2023-11-07T05:31:56Z",
      "bounced_at": "2023-11-07T05:31:56Z",
      "replied_at": "2023-11-07T05:31:56Z",
      "current_node_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a"
    }
  ],
  "total": 4503599627370495,
  "page": 4503599627370495,
  "limit": 4503599627370495
}
CLI:
puffle campaign lead --id <id>
puffle campaign lead --id <id> --page <page> --limit <limit> --status <status>

Overview

A single URL that owns the lead roster for a campaign:
  • GET — paginated list with optional status filter. Safe to poll.
  • POST — bulk add leads (1–1000 per call). Deduplicates by email (email campaigns) or LinkedIn URL (LinkedIn campaigns).
  • PATCH — edit one lead’s profile fields.
  • DELETE — remove leads by ID list (up to 500) or wipe the roster with { all: true }.
Every write method requires the campaign to be in draft status. Once the campaign transitions to launching/active/paused/completed, the roster is frozen — the API will return 400 on any mutation.

AI agent notes

Channel identity. Every lead needs the identity field matching the campaign type: email for email campaigns, linkedin_url for LinkedIn campaigns. Rows missing the required identity are rejected with a 400 naming the offending index. The other identity field is captured opportunistically when provided and valid (so a LinkedIn campaign can still record email when present).Deduplication semantics. Add is deduped both within the batch and against existing campaign leads. Identity is normalized before comparison — emails are lowercased; LinkedIn URLs drop trailing slashes and query strings. Duplicates silently fall out of the import count; the response separates imported from duplicates.Custom fields. custom_fields is a free-form JSONB record rendered as %custom_fields.<key>% in message templates. Use this for per-lead data the built-in columns don’t cover (industry, deal size, notes).Stats sync. Add and delete both recompute campaigns.stats atomically after the write via the recalculate_campaign_stats RPC — no manual stat refresh needed.Polling the list. status filters map to pipeline stages. pending is the launch queue; sent/connection_sent/connected/message_sent are in-flight; replied/bounced/completed/timed_out/cancelled are terminal. Use total from the response to drive pagination — with under 200 results per page and up to 200 allowed as limit, a single fetch often suffices.Scaling. Pre-query caps at 100k existing leads for dedup. Above that the DB unique constraint takes over — behavior is unchanged, just slower on write.

Authorizations

Authorization
string
header
required

Bearer authentication header of the form Bearer <token>, where <token> is your auth token.

Path Parameters

id
string
required

Campaign UUID

Query Parameters

page
integer
Required range: 1 <= x <= 9007199254740991
limit
integer
Required range: 1 <= x <= 200
status
string

Response

Page of leads with total count.

prospects
object[]
required
total
integer
required
Required range: 0 <= x <= 9007199254740991
page
integer
required
Required range: 0 < x <= 9007199254740991
limit
integer
required
Required range: 0 < x <= 9007199254740991