PloidDocs
Search

Search

Describe who you're looking for and get back a list of matching people instantly. Search by role, company, skills, location, or anything else.

POST /v1/search

Use this endpoint for a one-shot list of up to 150 people. Provide a semantic query, at least one structured filter, or both. Larger num_results values and async requests hand off to a background People Set — see Async searches.

curl https://api.ploid.com/v1/search \
  -H "Authorization: Bearer $PLOID_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "software engineers at fintech companies in San Francisco",
    "type": "auto",
    "category": "people",
    "num_results": 25,
    "filters": {
      "title": "software engineer",
      "seniority": "senior",
      "industry": "fintech",
      "location": "San Francisco"
    },
    "contents": {
      "fields": ["linkedin", "title", "company", "location"]
    }
  }'

Search types:

  • instant — retrieve from Ploid's people index.
  • fast — overfetch and rerank index results for better recall.
  • auto — after index retrieval, resolve a named-person miss and research more candidates when results are thin; every result is verified.
  • deep — resolve a named-person miss and always research more candidates after index retrieval; every result is verified.

Hard filters

A hard filter is a condition every returned person must satisfy. Rows that contradict one are dropped. An unsupported filter is rejected; it is never skipped.

Set fields are combined with AND. The serving index accepts these:

FilterA person matches when
titleThe current job title holds that role. Matching uses word boundaries, so founder matches Co-Founder and does not match Executive Assistant to the Founder or Partner at Founders Fund.
companyThe current employer is that company after punctuation and legal suffixes (Inc, LLC, Ltd, and similar) are removed. Apple, Inc. matches Apple. Apple World does not match Apple, and Stripe Consulting does not match Stripe. Rebrand pairs match: Meta/Facebook, X/Twitter, and Google/Alphabet.
locationCity, state, and country contain every part of the value. San Francisco, CA satisfies California and CA. Short codes match as whole tokens, so US does not match Russia or Austin.
seniorityIndexed seniority or title matches the level. Recognized values are entry, junior, mid, senior, staff, principal, director, vp, and cxo. Close titles count: senior includes Sr. and Manager; director includes Head; cxo includes chief and founder titles. Any other value must appear in the seniority or title text.
industryThe indexed industry contains the value, case-insensitively. A blank industry does not match.

Each field takes one value, or an object with any_of (match any listed value) and none_of (drop a person whose stated value matches). location also accepts countries and cities, which join any_of as alternatives. Up to 10 values per list.

{
    "query": "fintech founders",
    "type": "auto",
    "filters": {
        "title": { "any_of": ["Founder", "CEO"] },
        "location": { "countries": ["United States"], "cities": ["New York"] },
        "company": {
            "none_of": ["Google"],
            "employee_count": { "gte": 10, "lte": 200 },
            "funding_stage": { "in": ["seed", "series_a"] }
        },
        "experience": { "past_companies": { "any_of": ["Stripe", "Ramp"] } },
        "education": { "schools": { "any_of": ["MIT", "Stanford"] } }
    }
}

The index does not hold employer headcount, funding stage, past employers, or schools. Those filters are proven per person with cited evidence instead:

FilterA person matches when
company.employee_count (gte, lte), or company_size (min, max)The current employer's headcount is in range.
company.funding_stage.inThe current employer's latest stage is listed: pre_seed, seed, series_a, series_b, series_c, series_d_plus, public, or bootstrapped.
experience.past_companies.any_ofA past role, not only the current one, was at a listed company.
education.schools.any_ofThe person attended a listed school.

Each one counts toward the 5-criteria limit and needs type auto or deep, or an async search; instant and fast return 422 unsupported_filters. A person whose evidence is unclear is not returned. Exclusions and evidence-verified filters narrow a search, so a request also needs a query or a positive title, company, location, seniority, or industry value.

company_domain, radius_km, and tenure return 422 unsupported_filters.

Criteria

criteria takes up to 5 plain-English conditions every result must satisfy, for example "Has founded a venture-backed company" (up to 500 characters each). Each criterion is researched and judged separately; every result carries one verification entry per criterion and is ranked by evidence strength across all of them. Criteria require verification — type auto or deep, or an async search — and count toward the same 5-condition limit as evidence-verified filters. In an async search they replace the criteria derived from the query.

Async searches (async: true or more than 150 results) also count each multi-value any_of clause — including a location whose cities and countries together list more than one place — and each nonempty none_of clause in filters as a condition toward the same limit; over 5 the request fails 422 too_many_criteria. Sync searches do not count those clauses.

Every result carries filter_match, one entry per requested filter. On instant and fast, basis is indexed_field: the stored profile satisfied it, with the observed value and the value that matched. On auto and deep, every filter is researched for every person, so basis is verified_evidence and the evidence is in verification.

instant and fast return index rows only. A blank title, company, or location fails that filter. Role, company, and place phrases parsed from query are hard on these types as well. Funding-stage words in query (for example, “Series B startups”) are never matched as company or headline text; on auto/deep they are verified like company.funding_stage. Qualifiers such as “ideally …”, “open to …”, sector descriptors (“fintech startups”), and “X or remote” places rank results instead of filtering them.

auto and deep research the query and still apply every explicit filter. When that research plan succeeds, phrases parsed from query are not a second hard gate. If planning falls back, those parsed phrases are hard, same as instant and fast. Every filter is checked again on the final list, and a stated mismatch is always dropped. seniority and industry filters are answered from the Ploid people index only.

A company inferred for a named person, such as Ada Lovelace at Analytical Engines, stays a ranking clue, so a stale employer cannot hide that person. An explicit filters.company is always hard.

data.verification reports how verification went: status is complete when the requested count was filled without a deadline — verification permits up to 60 seconds for auto and 120 seconds for deep. complete does not mean every candidate was checked; coverage.has_more can still be true. partial means the count was not filled or a deadline hit. candidates_checked counts the people it evaluated and search_time_ms its wall time. data.coverage says has_more when more candidates existed than were checked or returned, and counts them with candidates_seen and candidates_checked.

Main results always include person.person_id, which can be passed directly to POST /v1/person, plus person.resolution_source: "ploid". Retrieval does not verify current profile claims, so person.identity_verified is false until POST /v1/person returns evidence. score and person.confidence are the result's rank within the response, from 1 for the strongest match down. Contact fields are deliberately excluded and reported as person.contact_status: "requires_enrichment"; resolve them through POST /v1/enrich. On auto and deep, data.grounding.results holds one evidence entry per returned person, with the verification reasoning as its snippet.

Freshness

Verified searches (auto and deep) cache the public evidence behind each person. freshness.max_age_days caps how old that cached evidence may be:

{
    "query": "fintech founders in New York",
    "type": "auto",
    "num_results": 10,
    "freshness": { "max_age_days": 2 }
}

Every candidate's profile and page reads must then be at most 2 days old; anything older is re-read live before verification. max_age_days: 0 always re-reads live sources.

When freshness is omitted, verification derives a window from the time-sensitive facts the request's filters and criteria depend on — title, company, seniority, industry, and company_size mark the current role as time-sensitive (~90-day shelf life), location marks place (~180 days), and criterion wording such as "currently works at" or "based in" counts too. The free-text query does not set the window directly, but on auto/deep some query parts turn into verified conditions that do: a funding stage in the query ("Series B startups") adds a "Current employer's latest funding stage" condition, which marks the current role as time-sensitive. A default auto search with current-role facts lands around a 4.6-day window and deep around 1.8 days; a request with no decaying facts — a plain query with no funding stage, or only past-employer and school filters — keeps the relaxed 7-day cache. The derived window applies to the top-ranked candidates (num_results on auto, twice that on deep); the rest use the relaxed window.

The response echoes what was applied under data.request.freshness: max_age_days is the window in days (fractional when derived) and source is request or default.

Tighter windows mean more live reads, so a tight search can take longer and return fewer results within its deadline. freshness requires verification: instant and fast return 422 unsupported_freshness.

Async searches

Requests larger than 150 results — num_results up to 5,000 — cannot run inline. They return 202 and start a background People Set; self-serve sets support up to 500 results and larger requests return 402 count_requires_sales. async: true starts the same hand-off at any size, and async: false with a num_results above 150 is rejected. Async searches always verify each row, so type and contents do not apply. webhook_url is accepted on async searches only and receives the Set's progress events.

The 202 response carries the Set's handle:

{
    "data": {
        "mode": "async",
        "set_id": "cmvbc8roj008we2can3hjbex4",
        "search_id": "cmvbc8rom008ye2cawe6wbk26",
        "status": "running",
        "criteria": ["Role: engineer"],
        "pricing": {
            "search_credits_per_10_admitted": 1,
            "estimated_search_credits": 16,
            "note": "Charged on admitted rows (1 credit / 10). Enrichments are separate."
        },
        "set_url": "/v1/sets/cmvbc8roj008we2can3hjbex4",
        "items_url": "/v1/sets/cmvbc8roj008we2can3hjbex4/items",
        "events_url": "/v1/sets/cmvbc8roj008we2can3hjbex4/events"
    }
}

criteria lists the conditions every admitted row is verified against, and pricing is an estimate — billing settles on admitted rows. Page admitted rows from items_url while the search runs, or follow events_url for server-sent progress. See People Sets.

Choosing a public surface

NeedChoose
Immediate list, 150 or fewer results/v1/search
More than 150 results, durable research/v1/search with async, or Sets
Research one identified person/v1/person

Durable, multi-step list building uses People Sets (preview), which are available to API keys with the people:search scope.

Errors and limits

ConditionStatus and code
Category is not people422 unsupported_category
Filter is not in serving index422 unsupported_filters
Evidence-verified filter on instant or fast422 unsupported_filters
Criteria plus evidence-verified filters exceed 5422 too_many_criteria
Only exclusions or evidence-verified filters, no query422 unsupported_request
freshness on instant or fast422 unsupported_freshness
Fast search is not configured503 search_unavailable
Search timed out without results503 search_timeout
Search failed without results503 search_failed

POST /v1/search returns up to 150 results inline (num_results, default 10); larger requests and async: true return 202 and run as a background Set. The workspace plan and API key set the request rate; rejected calls return Retry-After and are never billed. Timed-out or shard-partial index responses fail closed with a 503; the endpoint does not return partial index results. See Errors & rate limits.