The Person Search API lets you find professionals by name, title, company, location, and more. This page walks you through the API step by step, starting with the simplest possible request and building up to advanced queries.
Every request goes to the same endpoint:
POST https://api.crustdata.com/person/search
Replace YOUR_API_KEY in each example with your actual API key. All
requests require the x-api-version: 2025-11-01 header.
Your first search: find a person by name
The simplest search finds a person by their exact name. You pass a single filter with the = operator.
curl --request POST \
--url https://api.crustdata.com/person/search \
--header 'authorization: Bearer YOUR_API_KEY' \
--header 'content-type: application/json' \
--header 'x-api-version: 2025-11-01' \
--data '{
"filters": {
"field": "basic_profile.name",
"type": "=",
"value": "Abhilash Chowdhary"
},
"limit": 1
}'
Response trimmed for clarity.
Understanding the response
Every search response has three fields:
profiles — an array of matching people. Each profile contains identity, employment, education, skills, and contact data.
total_count — how many people match your filters across the full database. Here, 8 people named “Abhilash Chowdhary” exist.
next_cursor — a pagination token. Pass it in the next request to get the next page of results. null means there are no more pages.
Combine filters with and
Real searches need more than one criterion. Wrap multiple conditions inside an op: "and" group to require all of them.
This search finds Co-Founders located in San Francisco. The (.) operator does a regex/contains match instead of an exact match.
curl --request POST \
--url https://api.crustdata.com/person/search \
--header 'authorization: Bearer YOUR_API_KEY' \
--header 'content-type: application/json' \
--header 'x-api-version: 2025-11-01' \
--data '{
"filters": {
"op": "and",
"conditions": [
{
"field": "experience.employment_details.title",
"type": "(.)",
"value": "Co-Founder"
},
{
"field": "basic_profile.location.full_location",
"type": "(.)",
"value": "San Francisco"
}
]
},
"limit": 2
}'
Response trimmed for clarity.
The key difference from the first example: instead of a single filters object, you now have a group with op: "and" and a conditions array. Every condition must match for a profile to be included.
Search by employer and title
This is the most common pattern for sales and recruiting: find people with a specific title at a specific company. This search finds VPs, Directors, and Heads of department at Retool.
curl --request POST \
--url https://api.crustdata.com/person/search \
--header 'authorization: Bearer YOUR_API_KEY' \
--header 'content-type: application/json' \
--header 'x-api-version: 2025-11-01' \
--data '{
"filters": {
"op": "and",
"conditions": [
{
"field": "experience.employment_details.company_name",
"type": "in",
"value": ["Retool"]
},
{
"field": "experience.employment_details.title",
"type": "(.)",
"value": "VP|Director|Head of"
}
]
},
"limit": 1
}'
Response trimmed for clarity.
How the operators work
There are two different operators at play here:
in on experience.employment_details.company_name checks if the person has worked at any of the listed companies (current or past). Pass an array even for a single company. To search only current employers, use experience.employment_details.current.company_name instead.
(.) on experience.employment_details.title does a regex match. The pipe | means “or”, so VP|Director|Head of matches any title containing “VP”, “Director”, or “Head of”. To search only current titles, use experience.employment_details.current.title instead.
The experience.employment_details.company_name field includes all employers (current and past). If you see someone whose current role is at a different company, it means they previously worked at your target company.
Exclude specific titles
Sometimes you want everyone at a company except certain roles. Use the not_in operator to exclude titles.
This search finds people at OpenAI or Retool but excludes interns and students.
curl --request POST \
--url https://api.crustdata.com/person/search \
--header 'authorization: Bearer YOUR_API_KEY' \
--header 'content-type: application/json' \
--header 'x-api-version: 2025-11-01' \
--data '{
"filters": {
"op": "and",
"conditions": [
{
"field": "experience.employment_details.company_name",
"type": "in",
"value": ["OpenAI", "Retool"]
},
{
"field": "experience.employment_details.title",
"type": "not_in",
"value": ["Intern", "Student"]
}
]
},
"limit": 2
}'
The not_in operator removes any profile where one of the listed values appears in their title history. This is useful for cleaning up results in recruiting or sales workflows.
Search within a geographic radius
The geo_distance filter finds people within a specific distance of a city. This is powerful for territory-based sales or local recruiting.
This search finds CTOs within 10 miles of San Francisco.
curl --request POST \
--url https://api.crustdata.com/person/search \
--header 'authorization: Bearer YOUR_API_KEY' \
--header 'content-type: application/json' \
--header 'x-api-version: 2025-11-01' \
--data '{
"filters": {
"op": "and",
"conditions": [
{
"field": "professional_network.location.raw",
"type": "geo_distance",
"value": {
"location": "San Francisco",
"distance": 10,
"unit": "mi"
}
},
{
"field": "experience.employment_details.current.title",
"type": "(.)",
"value": "CTO|Chief Technology"
}
]
},
"limit": 1
}'
Response trimmed for clarity.
How geo_distance works
The geo_distance filter uses the professional_network.location.raw field. The value is an object with three fields:
Field Required Description locationYes City name or region (e.g., “San Francisco”, “London”, “New York”) distanceYes Radius from the center point unitNo Distance unit: mi, km, m, ft. Defaults to km
Search by country
For broader geographic targeting, filter by country directly.
curl --request POST \
--url https://api.crustdata.com/person/search \
--header 'authorization: Bearer YOUR_API_KEY' \
--header 'content-type: application/json' \
--header 'x-api-version: 2025-11-01' \
--data '{
"filters": {
"field": "basic_profile.location.country",
"type": "=",
"value": "United States"
},
"limit": 2
}'
This returns all people located in the United States. With 125M+ matching profiles, you will want to combine this with title or employer filters to narrow results.
Paginate through results
When your search matches more profiles than your limit, use cursor-based pagination to walk through all pages.
First page: send your normal search request.
curl --request POST \
--url https://api.crustdata.com/person/search \
--header 'authorization: Bearer YOUR_API_KEY' \
--header 'content-type: application/json' \
--header 'x-api-version: 2025-11-01' \
--data '{
"filters": {
"field": "experience.employment_details.company_name",
"type": "in",
"value": ["Retool"]
},
"limit": 100
}'
Next page: take the next_cursor value from the response and pass it in your next request. Keep the same filters and limit.
curl --request POST \
--url https://api.crustdata.com/person/search \
--header 'authorization: Bearer YOUR_API_KEY' \
--header 'content-type: application/json' \
--header 'x-api-version: 2025-11-01' \
--data '{
"filters": {
"field": "experience.employment_details.company_name",
"type": "in",
"value": ["Retool"]
},
"limit": 100,
"cursor": "PASTE_NEXT_CURSOR_VALUE_HERE"
}'
Continue until next_cursor is null, which means you have reached the last page.
Always include sorts when paginating to ensure stable ordering across
pages.
Sort results
Use the sorts parameter to order results by a specific field. This is important for stable pagination.
Sort by connections (descending)
curl --request POST \
--url https://api.crustdata.com/person/search \
--header 'authorization: Bearer YOUR_API_KEY' \
--header 'content-type: application/json' \
--header 'x-api-version: 2025-11-01' \
--data '{
"filters": {
"field": "experience.employment_details.current.title",
"type": "=",
"value": "CEO"
},
"sorts": [{"field": "professional_network.connections", "order": "desc"}],
"limit": 5,
"fields": ["basic_profile.name", "professional_network.connections"]
}'
Valid sortable fields include: crustdata_person_id, basic_profile.name, professional_network.connections, experience.employment_details.start_date, experience.employment_details.company_id, metadata.updated_at.
Exclude specific people from results
Use post_processing to remove known profiles from results. This is useful when re-running searches and you want to skip people you have already contacted.
curl --request POST \
--url https://api.crustdata.com/person/search \
--header 'authorization: Bearer YOUR_API_KEY' \
--header 'content-type: application/json' \
--header 'x-api-version: 2025-11-01' \
--data '{
"filters": {
"field": "experience.employment_details.title",
"type": "(.)",
"value": "Founder"
},
"limit": 5,
"post_processing": {
"exclude_names": ["Ali Kashani"],
"exclude_profiles": ["https://www.linkedin.com/in/alikashani"]
}
}'
You can exclude by name, by LinkedIn URL, or both.
Preview mode
Use preview: true to get lightweight results before committing credits. Preview responses have the same structure but may return fewer fields.
curl --request POST \
--url https://api.crustdata.com/person/search \
--header 'authorization: Bearer YOUR_API_KEY' \
--header 'content-type: application/json' \
--header 'x-api-version: 2025-11-01' \
--data '{
"filters": {
"field": "experience.employment_details.title",
"type": "(.)",
"value": "Founder"
},
"preview": true,
"limit": 2
}'
Filter operator reference
Operator Meaning Example use =Exact match basic_profile.name = “David Hsu”!=Not equal Exclude a specific country > / <Greater/less than Numeric comparisons inValue is in list experience.employment_details.company_name in [“Retool”, “OpenAI”]not_inValue is not in list Exclude titles like “Intern” (.)Regex/contains match Title contains “VP|Director” geo_distanceWithin radius of location People near San Francisco
Searchable fields
Field What it matches Best for basic_profile.namePerson’s full name Finding a specific person basic_profile.location.countryCountry name Country-level targeting basic_profile.location.full_locationFull location string City or region targeting experience.employment_details.titleAll titles (current + past) Role-based prospecting experience.employment_details.current.titleCurrent title only Current role targeting experience.employment_details.company_nameAll employers (current + past) Company-based targeting experience.employment_details.current.company_nameCurrent employer only Current company targeting professional_network.location.rawGeographic coordinates Radius-based search (with geo_distance)
Response fields
Each profile in the response contains these sections:
Section Key fields Description basic_profilename, headline, current_title, location, summaryIdentity and location experienceemployment_details.current, employment_details.pastFull work history educationschools, all_schools, all_degreesEducation background skillsprofessional_network_skillsListed skills contactemails, websitesAvailable contact data social_handlesprofessional_network_identifier.profile_urlLinkedIn profile URL professional_networkconnections, profile_picture_permalinkNetwork metadata metadatalast_scraped_source, updated_atData freshness timestamps
Request parameter reference
Parameter Type Required Default Description filtersobject Yes — Filter condition or condition group. See operators above. fieldsstring[] No All Dot-path fields to return (e.g., ["basic_profile.name", "experience.employment_details.current.title"]). sortsarray No []Sort specifications as an array of { field, order } objects. Use asc or desc for order. Required for stable pagination. limitinteger No 20 Max profiles per page (1–1000). countinteger No — Alias for limit. cursorstring No nullPagination cursor from previous response’s next_cursor. post_processingobject No — exclude_profiles (URL array) and exclude_names (name array).previewboolean No falseReturn lightweight results (faster, lower cost). return_queryboolean No falseDebug flag — include the compiled search query in the response.
Errors
Status Meaning 400Invalid request — unsupported field, wrong operator, or malformed filters. 401Invalid or missing API key. 403Permission denied or insufficient credits. 500Internal server error. Retry with exponential backoff.
No results
When no people match the filters, the API returns 200 with an empty profiles array:
{
"profiles" : [],
"next_cursor" : null ,
"total_count" : 0
}
Action: Broaden filters or check field values with Autocomplete .
API reference summary
Detail Value Endpoint POST /person/searchAuth Bearer token + x-api-version: 2025-11-01 Response { "profiles": [...], "next_cursor": "...", "total_count": N }Pagination Cursor-based. Pass next_cursor as cursor. Stop when next_cursor is null. Errors 400, 401, 403, 500
What to do next
Enrich a profile — once you have a LinkedIn URL from search, use Person Enrich to get the full cached profile.
Discover filter values — use Person Autocomplete to find exact indexed values for search filters.
See more examples — browse Person Examples for ready-to-copy workflow patterns.
Read the quickstart — see Person APIs for a high-level guide to the core person endpoints.
Check the API reference — see Search person API reference for the full schema.