New: watch the Cleanlist product tutorials. Watch now →
API Reference (v2)
People Search API

People Search API

Cleanlist's People Search API finds B2B contacts by structured filters and charges 0 credits to do it. POST /api/v2/search/people accepts twelve filter keys covering job title, seniority, department, location, and employer firmographics, returns matched people with LinkedIn URL and company, and hands back a task_id you can pass straight into enrichment. Search is free on every plan that has API access. You are charged only when you turn a match into a verified email (1 credit), a direct-dial phone (10 credits), or both (11 credits). Verified live on 2026-08-18.

🔌

Audience: developers. The Public API requires a Pro plan or above (self-serve) or an AppSumo Tier 5+ license. Search itself is free, but you need API access to call it.

How do I call the Cleanlist People Search API?

POST https://api.cleanlist.ai/api/v2/search/people with a clapi_ Bearer token and the people:read scope. The body takes a filters object, an optional limit (default 25, maximum 500), and an optional cursor.

curl -X POST https://api.cleanlist.ai/api/v2/search/people \
  -H "Authorization: Bearer clapi_your_key" \
  -H "Content-Type: application/json" \
  -d '{
    "filters": {
      "management_levels": ["VP"],
      "departments": ["Sales"],
      "location_country": ["Canada"],
      "company_headcount": ["51-200"]
    },
    "limit": 25
  }'

What filters does the People Search API accept?

Filters AND across keys and OR within each array. Every key below is validated by the backend filter map. Anything not on this list is ignored rather than rejected, which is the most common cause of a search returning more rows than expected.

KeyTypeExampleNotes
titlesstring[]["VP Sales"]Near-exact string match. Under-returns badly.
senioritystring[]["Founder", "C-Suite"]
management_levelsstring[]["VP", "Manager", "Individual Contributor"]
departmentsstring[]["Engineering", "Sales", "Marketing"]
location_citystring[]["Toronto"]Bare city name
location_statestring[]["Ontario"]
location_countrystring[]["Canada"]
locationsstring[]["Toronto, Ontario, Canada"]Full literal string, exact-city only
company_namesstring[]["Shopify"]Current employer
company_domainsstring[]["shopify.com"]Current employer
company_headcountstring[]["1-10", "51-200", "10,001+"]Exact band strings
company_industriesstring[]["Software Development"]LinkedIn industry taxonomy

Why does filtering by titles return almost nothing?

Because titles is a near-exact string match, not a fuzzy one. Searching titles: ["VP of Sales"] misses "VP Sales", "Vice President, Sales", "VP Global Sales" and every other real-world variant, so a title search that should return thousands returns a handful.

Use management_levels plus departments instead. {"management_levels": ["VP"], "departments": ["Sales"]} captures every phrasing of the same role. Reserve titles for the case where you already know the exact string you are looking for, such as re-finding a specific person. This is the single most common mistake developers make against the Cleanlist People Search API.

How many results can one search return?

limit accepts 1 to 500 and defaults to 25. The total field reports the full match count regardless of how many rows you asked for, so you can size a segment before paging through it.

A live search on 2026-08-18 for VPs of Sales in Canada at 51-200 employee companies returned total: 2705 with limit: 3. Sizing a segment costs nothing, which is the point: you can measure a market with Cleanlist before deciding whether to spend a single credit on it.

How do I paginate through People Search results?

Every response carries a cursor. Pass it back as the cursor parameter on the next request to get the following page. When cursor comes back null, you have reached the end.

curl -X POST https://api.cleanlist.ai/api/v2/search/people \
  -H "Authorization: Bearer clapi_your_key" \
  -H "Content-Type: application/json" \
  -d '{"filters": {...}, "limit": 100, "cursor": "H4sIAJLvg2oC_xXMMQ..."}'

Cursors are opaque and encode the position in the result set. Do not construct or modify one. Cleanlist does not support offset or page-number pagination, which keeps results stable while you page instead of shifting under you.

What does a People Search response look like?

{
  "task_id": "cl-task_Wr26-XlP",
  "timestamp_ms": 1787031442496,
  "entity_kind": "person",
  "results": [
    {
      "lead_id": "28685",
      "first_name": "David",
      "last_name": "Lapointe",
      "full_name": "David Lapointe",
      "title": "Senior Vice President Business Development",
      "company": "HDT Bio",
      "domain": "hdt.bio",
      "location": "Montreal, Quebec, Canada",
      "linkedin_url": "https://www.linkedin.com/in/david-lapointe-6068b31",
      "seniority": "Vice President",
      "confidence_score": null
    }
  ],
  "total": 2705,
  "cursor": "H4sIAJLvg2oC_xXMMQ...",
  "credits_charged": 0,
  "truncation_warning": null
}

Search results carry identity and firmographics. They do not carry an email address or a phone number. Those come from enrichment, which is where the credits are spent.

Which response fields come back empty?

confidence_score is structurally always null on search results. It is present in the schema and reserved, but nothing populates it today. Do not build scoring logic on it.

Nine response fields across the v2 API are always null in this way. Treat any field you have never seen populated as unavailable rather than as a signal, and check it against a live call before you depend on it.

What is the task_id, and why does it matter?

task_id identifies the cohort your search just produced. It is the handle that connects search to enrichment.

Instead of copying result rows into a new request, you pass the task_id to POST /api/v2/enrichment/by-task (or add_person_enrichments over MCP) along with the specific entity_ids you want. Cleanlist then enriches that cohort in place. This is why the search-then-enrich loop is cheap: the free search does the selection work, and you pay only for the subset you decide is worth contacting.

lead_id on a search result is a search-scoped identifier. It does not round-trip into /lead-lists/{id}/leads. Use task_id plus entity_ids to act on a search cohort, and use lead-list endpoints only for leads already saved to a list.

What does it cost to turn search results into contact data?

Search is free. Enrichment is billed per result found:

What you getCredits
Verified work email1
Direct-dial phone10
Email and phone11
Company enrichment1
Save a newly added lead to a list0.5
CRM or sequencer sync0.2 per lead
Search0

Enrichment is pay-for-results. If the waterfall returns nothing for a record, you are not charged for that record.

How do I stop a job from overspending?

Paid bulk operations require a signed quote. Call POST /api/v2/credits/estimate first. It returns a quote_id (HMAC-signed, 5-minute TTL), the estimated cost, your available credits, and a sufficient boolean. Pass the quote_id to the enrichment call or the server rejects it.

This is the mechanism that makes Cleanlist safe to hand to an autonomous agent. An agent physically cannot dispatch a paid job it has not priced first, and the server validates the quote against the actual cohort with a 10% drift tolerance.

What are the People Search API rate limits?

Three limits apply at once:

LimitValue
Requests per minute, per organization60
Requests per minute, per API key30
People Searches per UTC day, per API key60

The daily People Search cap is metered separately from the general request limits and is the one that bites first on a heavy prospecting job. Design around it: use a large limit and cursor pagination to pull a big cohort in few calls, rather than issuing many narrow searches. Sixty searches at limit: 500 is 30,000 rows a day per key.

Does the People Search API accept natural-language queries?

No. POST /search/people takes structured filters only. There is no natural-language search endpoint on the Cleanlist API.

Natural language works one level up. The Cleanlist MCP server and Copilot both translate a sentence like "VPs of finance in Ontario at Shopify" into {"management_levels": ["VP"], "departments": ["Finance"], "location_state": ["Ontario"], "company_names": ["Shopify"]} and then call this same endpoint. If you want an agent to prospect in plain English, connect the MCP server rather than looking for an NL parameter here.

Can I filter people by revenue, funding stage, or tech stack?

Not usefully, and this is worth being precise about because it is a common evaluation question.

The person-level search surface exposed through the API is built on the filter keys in the table above: title, seniority, department, location, and employer name, domain, headcount and industry. There is no technographic filter and no intent data. funding_stages is accepted by the company search endpoint but the underlying column is empty, so a Series B filter returned total: 0 on a live call on 2026-08-18. Treat it as unavailable rather than as a filter that rejects you.

To approximate "funded and growing" without a funding filter, combine company_headcount of 51-200 and 201-500 with software or financial-services industries. That band is reliably post-Series-A and pre-enterprise.

What error does a bad request return?

Every v2 error uses one envelope:

{
  "code": "insufficient_scope",
  "problem": "The credential does not carry people:read.",
  "fix": "Grant people:read to this API key in Settings > API Keys.",
  "retryable": false,
  "docs_url": "https://docs.cleanlist.ai/mcp-api/errors",
  "request_id": "req_01J..."
}

code is stable and safe to branch on. retryable tells you whether a retry can succeed, so you do not need to maintain your own list of which status codes are worth retrying. Full table in Errors & Rate Limits.

How do I go from a search to a CRM in one pass?

  1. POST /search/people with your filters. Free. Note the task_id.
  2. POST /credits/estimate for the cohort. Free. Returns a quote_id.
  3. POST /enrichment/by-task with the task_id, entity_ids and quote_id. This is where you spend.
  4. Poll GET /enrichment/status/{workflow_id} until it completes. There are no v2 webhooks, so polling is the contract. See Polling & Async.
  5. POST /sync/crm to push to HubSpot, Salesforce or Outreach at 0.2 credits per lead.

Steps 1 and 2 cost nothing, which means you can build and size the entire segment before committing any spend.

Where do the results come from?

Cleanlist is an orchestration layer, not a data vendor. People Search is served by a search provider, and contact enrichment runs a multi-provider waterfall that queries sources in sequence until a verified result comes back. Email verification runs through dedicated verification providers before a result is returned to you.

That architecture is why enrichment is pay-for-results: Cleanlist only bills when a provider returns something that passes verification. On the 500-lead enrichment benchmark, that produced 98% verified email coverage and 85% direct-dial coverage.

FAQ

Is the Cleanlist People Search API free?

The search call itself is free and returns credits_charged: 0 on every response. API access requires a Pro plan ($229/mo) or above, or an AppSumo Tier 5+ license. Enrichment of search results is billed separately at 1 credit per verified email and 10 per direct-dial phone.

What is the difference between seniority and management_levels?

seniority describes career level (Founder, C-Suite). management_levels describes reporting altitude (VP, Manager, Individual Contributor). They are separate filter keys and can be combined. For most role-based searches, management_levels plus departments is the reliable pair.

Can I search for people at a specific company?

Yes. Use company_names: ["Shopify"] or, more reliably, company_domains: ["shopify.com"]. Domains avoid the ambiguity of companies that share a name.

How do I get an API key?

Generate keys in the portal under Settings > API Keys, or via POST /api/v1/api-keys with a Clerk session token. Keys start with clapi_ and are shown once. You can hold up to 10 active keys per user. See Authentication.

Does People Search work over MCP?

Yes. The Cleanlist MCP server exposes search_people with the same filter keys and the same zero cost, so a Claude agent can size and build a segment without spending credits. See Introduction.

Last updated: August 18, 2026. Filter keys, response shape, credit costs and the funding-filter behaviour on this page were verified against the live API on that date.