Skip to main content
POST
/
api
/
leads
/
search
/
{id}
/
add-to-list
Create a list from search results
curl --request POST \
  --url https://app.puffle.ai/api/leads/search/{id}/add-to-list \
  --header 'Authorization: Bearer <token>' \
  --header 'Content-Type: application/json' \
  --data '
{
  "name": "<string>"
}
'
{ "list": { "id": "b1b2b3b4-1111-1111-1111-111111111111", "name": "Q2 SaaS VPs Austin" }, "insertedCount": 712, "duplicateCount": 130, "companyCount": 481 }

Overview

Creates a new list named by the caller and imports every result of a completed lead search into it. Runs synchronously (60 s timeout) — returns once every row is inserted or the operation rolls back. What the import does:
  1. Fetches all lead_search_results rows for the search (paginated at 1,000 per page).
  2. Extracts unique companies by company_domain and inserts them into list_company_rows.
  3. For each person, applies cross-list deduplication against every other list the caller owns. The three-tier match cascade is: LinkedIn URL → full name plus company → full name plus location.
  4. Inserts non-duplicate rows into list_rows, linking each to its company via company_row_id.
  5. On any failure, rolls back the whole write — companies, people, and the newly-created list — so callers never end up with an orphaned empty list.
If every result is a duplicate, the new list is rolled back and the call returns 409 with "All leads already exist in your lists".

AI agent notes

When to use this vs. POST /api/lists/{id}/add-search-results:
  • Use this endpoint when you want a new list named after the search — typical after the “search → list → campaign” journey.
  • Use the list-scoped variant when the caller already has a list in mind (e.g. an existing pilot cohort) and wants the search to merge in.
Pre-conditions:
  • The search must be status === "completed" — otherwise the call returns 400.
  • The search must have result_count > 0 — otherwise 400.
  • List name must be non-empty.
Expect duplicates. The duplicateCount in the response tells you how many rows were skipped by cross-list dedup. If duplicateCount > insertedCount, politely flag this to the human — they probably meant to search a different cohort.No BrightData enrichment. Apify data is considered rich enough; if the caller needs deeper enrichment (emails, phones), add an enrichment column to the new list instead.Idempotency: unsafe. Retrying the same call after a successful 200 will create a second list with the same name and (because cross-list dedup now matches) insertedCount = 0, duplicateCount = resultCount. Inspect total / duplicateCount before retrying on a network error.
See also: Start a search · Get a search · Agent Playbook.

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

Lead search UUID. Must be owned by the caller and have status === "completed".

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)$

Body

application/json

Creates a new List and imports the search results into it. If you want to merge into an existing list, use POST /api/lists/{id}/add-search-results instead.

name
string
required

Display name for the new List. Must be non-empty.

Minimum string length: 1

Response

List created and populated.

Import summary. The new list is fully populated synchronously — no background task to poll.

list
object
required

The newly-created list.

insertedCount
integer
required

People rows inserted after cross-list deduplication. May be less than search.result_count.

Required range: 0 <= x <= 9007199254740991
duplicateCount
integer
required

People filtered out because they already exist in another list owned by the caller (cross-list dedup — matches on LinkedIn URL, full name + company, or full name + location).

Required range: 0 <= x <= 9007199254740991
companyCount
integer
required

Unique companies extracted by company_domain and inserted into list_company_rows. Each person row is linked to its company via company_row_id.

Required range: 0 <= x <= 9007199254740991