Skip to main content
GET
/
api
/
campaigns
/
{id}
/
leads
List campaign prospects
curl --request GET \
  --url https://app.puffle.ai/api/campaigns/{id}/leads \
  --header 'Authorization: Bearer <token>'
{
  "prospects": [
    {
      "id": "7a1e8a2c-1234-4567-89ab-cdef01234567",
      "campaign_id": "8c2b2d4e-e29b-41d4-a716-446655440000",
      "user_id": "11111111-1111-1111-1111-111111111111",
      "status": "pending",
      "email": "[email protected]",
      "linkedin_url": null,
      "first_name": "Jane",
      "last_name": "Doe",
      "name": "Jane Doe",
      "company": "Acme",
      "position": "VP of Sales",
      "headline": null,
      "custom_fields": null,
      "source": "api",
      "source_ref": null,
      "created_at": "2026-04-22T10:00:00Z",
      "updated_at": "2026-04-22T10:00:00Z"
    },
    {
      "id": "8b2e8a2c-1234-4567-89ab-cdef01234568",
      "campaign_id": "8c2b2d4e-e29b-41d4-a716-446655440000",
      "user_id": "11111111-1111-1111-1111-111111111111",
      "status": "sent",
      "email": "[email protected]",
      "linkedin_url": null,
      "first_name": "Sam",
      "last_name": "Smith",
      "name": "Sam Smith",
      "company": "Acme",
      "position": "Director",
      "headline": null,
      "custom_fields": {
        "industry": "SaaS"
      },
      "source": "csv",
      "source_ref": "lead-list-id-123",
      "created_at": "2026-04-22T10:01:00Z",
      "updated_at": "2026-04-22T10:05:00Z"
    }
  ],
  "total": 120,
  "page": 1,
  "limit": 100
}

Overview

A single URL that owns the prospect roster for a campaign:
  • GET — paginated list with optional status filter. Safe to poll.
  • POST — bulk add prospects (1–1000 per call). Deduplicates by email (email campaigns) or LinkedIn URL (LinkedIn campaigns).
  • PATCH — edit one prospect’s profile fields.
  • DELETE — remove prospects 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 prospect 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 prospects. 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-prospect 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 prospects 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<uuid>
required

Campaign UUID

Pattern: ^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$

Query Parameters

page
integer

1-indexed page number. Defaults to 1.

Required range: 1 <= x <= 9007199254740991
limit
integer

Page size. Defaults to 100. Maximum 200 (values are clamped).

Required range: 1 <= x <= 200
status
string

Filter by prospect status (e.g. pending, sent, replied, bounced). Omit to list every prospect regardless of status.

Response

Page of prospects with total count for pagination math.

prospects
object[]
required
total
integer
required

Total prospects matching the filter (ignoring pagination). Use for calculating Math.ceil(total / limit) pages.

Required range: 0 <= x <= 9007199254740991
page
integer
required
Required range: 0 < x <= 9007199254740991
limit
integer
required
Required range: 0 < x <= 9007199254740991