Skip to main content
POST
/
api
/
campaigns
/
{id}
/
leads
/
import-from-list
Import Leads from List to Campaign
curl --request POST \
  --url https://app.puffle.ai/api/campaigns/{id}/leads/import-from-list \
  --header 'Authorization: Bearer <token>' \
  --header 'Content-Type: application/json' \
  --data '
{
  "listId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
  "filter": {
    "leadIds": [
      "<string>"
    ],
    "where": [
      {
        "field": "<string>",
        "value": "<unknown>"
      }
    ],
    "orderBy": [
      {
        "field": "<string>"
      }
    ],
    "limit": 2500
  }
}
'
{
  "imported": 4503599627370495,
  "duplicates": 4503599627370495,
  "invalid": 4503599627370495,
  "errors": 4503599627370495,
  "total": 4503599627370495,
  "partial": true
}
CLI:
puffle campaign lead import --id <id> --list-id <list-id>
puffle campaign lead import --id <id> --list-id <list-id> --filter <filter>

Overview

Hydrates canonical Leads from a saved List into the campaign as leads. The server pages through /api/lists/{id}/leads style canonical memberships and maps each Lead into the campaign lead shape.
  • LinkedIn campaigns use the Lead’s LinkedIn social URL.
  • Email campaigns use the Lead’s work-email contact or email attribute when present.
  • Profile fields come from canonical Lead fields and Lead attributes.
Invalid rows (missing the required identity for the campaign channel) are skipped. Duplicates are caught by the DB unique constraint. Campaign stats are recomputed atomically after insert.

AI agent notes

Channel-specific validation. Email campaigns require a usable email on the Lead. LinkedIn campaigns require a real LinkedIn URL on the Lead’s socials.Response shapes.
  • 200 — import succeeded (fully or partially); imported, duplicates, invalid, errors, total all returned.
  • 206 — a page fetch failed mid-run after earlier rows had already committed. partial: true. Retry with the same listId to pick up remaining rows — already-imported leads will be rejected as duplicates.
  • 422 — the list was readable but zero leads could be imported: every row invalid, every row a duplicate, or a mix of the two with zero new inserts. The response body still contains the count breakdown.
  • 500 — failed to fetch any list rows, or every insert batch errored.
Partial success guard. If pagination errors after some writes, the server returns 206 with partial: true rather than rolling back. Agents should surface this as “imported so far” in UI.Dedup semantics. Same as addLeadsToCampaign — the unique constraint is (campaign_id, email) for email campaigns and (campaign_id, linkedin_url) for LinkedIn campaigns.Memory. Paginated fetch + batched insert keeps memory bounded for large lists.

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

Body

application/json
listId
string<uuid>
required
filter
object

Response

Import succeeded with row-count breakdown.

imported
integer
required
Required range: 0 <= x <= 9007199254740991
duplicates
integer
required
Required range: 0 <= x <= 9007199254740991
invalid
integer
required
Required range: 0 <= x <= 9007199254740991
errors
integer
required
Required range: 0 <= x <= 9007199254740991
total
integer
required
Required range: 0 <= x <= 9007199254740991
partial
boolean