Skip to main content
POST
/
api
/
leads
/
search
Start a lead search
curl --request POST \
  --url https://app.puffle.ai/api/leads/search \
  --header 'Authorization: Bearer <token>' \
  --header 'Content-Type: application/json' \
  --data '
{
  "contact_job_title": [
    "<string>"
  ],
  "contact_not_job_title": [
    "<string>"
  ],
  "seniority_level": [
    "founder"
  ],
  "functional_level": [
    "<string>"
  ],
  "contact_location": [
    "<string>"
  ],
  "contact_city": [
    "<string>"
  ],
  "contact_not_location": [
    "<string>"
  ],
  "contact_not_city": [
    "<string>"
  ],
  "company_domain": [
    "<string>"
  ],
  "size": [
    "<string>"
  ],
  "company_industry": [
    "<string>"
  ],
  "company_not_industry": [
    "<string>"
  ],
  "company_keywords": [
    "<string>"
  ],
  "company_not_keywords": [
    "<string>"
  ],
  "min_revenue": "100K",
  "max_revenue": "100K",
  "funding": [
    "<string>"
  ],
  "fetch_count": 1250,
  "search_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
  "nl_query": "<string>",
  "listId": "3c90c3cc-0d44-4b50-8888-8dd25736052a"
}
'
{ "search": { "id": "3c90c3cc-0d44-4b50-8888-8dd25736052a", "user_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a", "list_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a", "title": "<string>", "title_edited": true, "status": "pending", "filters": {}, "apify_run_id": "<string>", "result_count": 4503599627370495, "error_message": "<string>", "created_at": "2023-11-07T05:31:56Z", "completed_at": "2023-11-07T05:31:56Z" } }

Overview

Creates a new search or resets an existing one (when search_id is supplied) and dispatches the lead-search Trigger.dev task against the Apify leads-finder actor. The flow is asynchronous: the response returns a search row with status: "pending". Poll GET /api/leads/search/{id} until status === "completed", then fold results into a list via POST /api/leads/search/{id}/add-to-list (or POST /api/lists/{id}/add-search-results to merge into an existing list). Fetches up to 2500 rows per run (the server clamps fetch_count to that ceiling). Typical Apify run time is 30 seconds to 5 minutes.

Binding to a list

Supplying listId binds the search to a list so results auto-fold in on completion. Only one active search per list — a second concurrent POST returns 409.
This operation shares the URL path /api/leads/search with the list verb. See List lead searches to enumerate the caller’s recent searches.

AI agent notes

The canonical lead-discovery journey:
  1. POST /api/leads/parse-filters — convert the human’s natural-language prompt into a structured filter object.
  2. POST /api/leads/search — pass those filters plus the original nl_query so the title reads well.
  3. GET /api/leads/search/:id — poll every 5 to 10 s until status === "completed".
  4. POST /api/leads/search/:id/add-to-list — turn results into a new List (or POST /api/lists/:id/add-search-results for an existing one).
  5. Continue into the campaign-launch journey.
Pre-flight checks:
  • fetch_count is clamped at 2500 — do not bother asking for more.
  • Revenue bands are enumerated (100K to 10B); sending min_revenue > max_revenue is silently swapped server-side.
  • Free-form qualifiers in filters ("actively hiring", "seeking pipeline", etc.) are not supported. Use parseLeadFilters to validate intent before submission and surface the unsupported string back to the human.
Polling cadence: every 5 to 10 s while status is pending or running. Most runs finish within 2 minutes; give up at 10 minutes and escalate with the correlationId.On 409: another search is active for the list. Wait for it to complete, or DELETE it first.
See also: Parse natural-language filters · Get a search · Add results to a list · Agent Playbook.

Authorizations

Authorization
string
header
required

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

Body

application/json

Filters merge with the top-level fields — the server extracts search_id, nl_query, and listId, then treats the rest as structured filters.

contact_job_title
string[]
contact_not_job_title
string[]
seniority_level
enum<string>[]

Apollo-style seniority bucket.

Available options:
founder,
owner,
c_suite,
partner,
vp,
head,
director,
senior,
manager,
entry
functional_level
string[]
contact_location
string[]
contact_city
string[]
contact_not_location
string[]
contact_not_city
string[]
company_domain
string[]
size
string[]

Company headcount bands, e.g. 1-10, 11-20, 21-50.

company_industry
string[]
company_not_industry
string[]
company_keywords
string[]
company_not_keywords
string[]
min_revenue
enum<string>

Annual revenue band (ordered). Server swaps min_revenue and max_revenue automatically if they arrive out of order.

Available options:
100K,
500K,
1M,
5M,
10M,
25M,
50M,
100M,
500M,
1B,
5B,
10B
max_revenue
enum<string>

Annual revenue band (ordered). Server swaps min_revenue and max_revenue automatically if they arrive out of order.

Available options:
100K,
500K,
1M,
5M,
10M,
25M,
50M,
100M,
500M,
1B,
5B,
10B
funding
string[]
fetch_count
integer

Maximum rows to fetch from the Apify actor. Server-clamped to [1, 2500]; defaults to 2500 when omitted.

Required range: 0 < x <= 2500
search_id
string<uuid>

Set to re-run an existing search with fresh filters. Previous lead_search_results rows are hard-deleted before the new Apify run dispatches. Ownership is enforced.

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

Original natural-language query (e.g. "VPs of Sales at SaaS companies in Austin"). Feeds the title generator so the resulting search title matches the user's intent; also helpful for observability.

listId
string<uuid>

Bind this search to a List. On completion the Trigger task auto-folds results via addSearchResultsToList(). Only one active search per list — a second concurrent call returns 409.

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

Response

Existing search reset and re-dispatched (body included search_id).

Search created (or reset, for re-runs). Status is pending; the Trigger.dev lead-search task owns execution from here. Poll GET /api/leads/search/{id} for progress.

Canonical lead search entity.