How to Build a People Search Pipeline with Cursor
A step-by-step guide to building a reusable people search pipeline in Cursor, from a first query to a ranked, enriched, exported shortlist.
Published
May 29, 2026
Written by
Abhilash Chowdhary
Reviewed by
Chris Pisarski
Read time
7
minutes

How to Build a People Search Pipeline with Cursor
Getting a people-data API working in production used to be a sequencing problem. You read the docs, wrote the client, handled auth and pagination, mapped the response, and only then found out whether the data was any good. A people search pipeline built in Cursor collapses that work. You point the editor at an API's documentation, describe the people you want in plain English, and it writes the integration for you.
One team we spoke with, a private-equity advisory group, used exactly this setup to turn an industry into a shortlist of executives. They needed people who had held a CEO, CFO, or COO title in the last five years at a specific set of companies, the kind of operators a fund puts forward for a board seat or to run a business after an acquisition. The whole integration started with one instruction typed into Cursor to get the API working. As the buyer put it, "If you send over the docs, I'm literally going to plug it into Cursor and see if it can solve it for me."
This guide builds that pipeline step by step, from a first working query to a ranked, exported shortlist with verified contact details. The same pipeline that finds board-ready executives also finds sales prospects and engineering hires, because the only thing that changes between those jobs is the search criteria. If you want to walk through the architecture with an engineer before you build, you can book a demo with Crustdata.
What a people search pipeline is, and why Cursor
A people search pipeline is a repeatable loop that takes search criteria and returns people who match, enriched and ready to use. It has five stages: resolve the companies you care about, search for people who fit your criteria, enrich the matches with contact and career detail, filter out anyone you have already seen, and export the result somewhere useful.
Built by hand, each stage is a script you maintain and rerun. In Cursor, the same loop becomes something you describe and adjust in the editor instead. Cursor is an AI code editor, and with a people-data API wired in (either through a Model Context Protocol server or a few lines of direct API code) it becomes the place where you describe a search, run it, read the results, and rerun it without switching tools.
The reason this matters for people data specifically is that the criteria are never right on the first try. You ask for "former CFOs," realize you also want people who were COO, notice the company name resolved to the wrong entity, and tighten the query. In an editor that writes and reruns the code for you, tightening the query is a quick edit. The pipeline below uses Crustdata as the worked example because its search filters map cleanly onto these criteria, but the architecture applies to any people-data provider with a search API.
Your first working query in five minutes
Before building the full pipeline, prove the data works with a single query. Open Cursor, add your Crustdata API key to a .env file, and describe the people you want. Here is a prompt that returns finance leaders at a specific company:
"Using the Crustdata people search API, find the current CFO at Stripe. Return name, title, and location."
Cursor translates that into a filter-based search. Under the hood, the request looks like this:
{ "filters": { "op": "and", "conditions": [ { "column": "current_employers.name", "type": "[.]", "value": "Stripe" }, { "column": "current_employers.title", "type": "[.]", "value": "Chief Financial Officer" } ] }, "limit": 5 }
{ "filters": { "op": "and", "conditions": [ { "column": "current_employers.name", "type": "[.]", "value": "Stripe" }, { "column": "current_employers.title", "type": "[.]", "value": "Chief Financial Officer" } ] }, "limit": 5 }
{ "filters": { "op": "and", "conditions": [ { "column": "current_employers.name", "type": "[.]", "value": "Stripe" }, { "column": "current_employers.title", "type": "[.]", "value": "Chief Financial Officer" } ] }, "limit": 5 }
Each condition is a column, a match type, and a value. The [.] operator is a substring match, so "Chief Financial Officer" also catches "Group Chief Financial Officer." You combine conditions with op: "and". The response comes back as a list of matching people with the fields you asked for, and you are charged only for results returned, with no charge when a query comes back empty. That single query is the whole pipeline in miniature, and every step that follows is about making it precise, repeatable, and exportable.
Step 1: Point Cursor at Crustdata
You can wire the data in two ways, and both end up calling the same API. Pick the one that fits how you like to work.
The direct-API path: keep your API key in .env and let Cursor write a small client. A search is a single POST request:
import os, requests resp = requests.post( "https://api.crustdata.com/screener/people/search", headers={"Authorization": f"Token {os.environ['CRUSTDATA_API_KEY']}"}, json={ "filters": { "op": "and", "conditions": [ {"column": "current_employers.name", "type": "[.]", "value": "Stripe"}, {"column": "current_employers.title", "type": "[.]", "value": "Chief Financial Officer"}, ], }, "limit": 5, }, ) people = resp.json()
import os, requests resp = requests.post( "https://api.crustdata.com/screener/people/search", headers={"Authorization": f"Token {os.environ['CRUSTDATA_API_KEY']}"}, json={ "filters": { "op": "and", "conditions": [ {"column": "current_employers.name", "type": "[.]", "value": "Stripe"}, {"column": "current_employers.title", "type": "[.]", "value": "Chief Financial Officer"}, ], }, "limit": 5, }, ) people = resp.json()
import os, requests resp = requests.post( "https://api.crustdata.com/screener/people/search", headers={"Authorization": f"Token {os.environ['CRUSTDATA_API_KEY']}"}, json={ "filters": { "op": "and", "conditions": [ {"column": "current_employers.name", "type": "[.]", "value": "Stripe"}, {"column": "current_employers.title", "type": "[.]", "value": "Chief Financial Officer"}, ], }, "limit": 5, }, ) people = resp.json()
The MCP path: register Crustdata's Model Context Protocol server in Cursor's mcp.json, using the install command from Crustdata's MCP setup docs. Cursor then calls the search as a native tool, and you never write the request body yourself. The configuration follows Cursor's standard MCP shape:
{ "mcpServers": { "crustdata": { "command": "<from Crustdata MCP docs>", "args": ["<from Crustdata MCP docs>"], "env": { "CRUSTDATA_API_KEY": "your_key_here" } } } }
{ "mcpServers": { "crustdata": { "command": "<from Crustdata MCP docs>", "args": ["<from Crustdata MCP docs>"], "env": { "CRUSTDATA_API_KEY": "your_key_here" } } } }
{ "mcpServers": { "crustdata": { "command": "<from Crustdata MCP docs>", "args": ["<from Crustdata MCP docs>"], "env": { "CRUSTDATA_API_KEY": "your_key_here" } } } }
The MCP path is the faster way to start because Cursor handles the request construction and pagination. The direct-API path gives you more control when you wrap the search in your own scripts. To confirm your key works and see how much headroom you have, ask Cursor to check your remaining credits, which is a free call to the /user/credits endpoint.
Step 2: Define who you're looking for
A pipeline is only reusable if the criteria live in one place. In Cursor, that place is a project rules file (.cursor/rules) describing the people you want in plain language, so every run starts from the same definition instead of a fresh prompt you half-remember.
For the executive-finder example, the rules file holds three things:
Titles that count: held the title CEO, CFO, or COO, written out in full and as abbreviations so the search catches both.
Recency: held that title within the last five years, which includes current officers and recent formers.
Target companies: the specific set of companies to run against, or the industry to break down into companies first.
Writing this down once does two jobs. It keeps every run consistent, and it gives Cursor the context to construct tighter queries without you re-explaining the brief each time. When you want the same pipeline to find sales prospects instead, you edit this one file rather than rebuilding anything.
Step 3: Resolve company names first
Before you search for people, resolve every company name to a canonical record, because the name you have is often not the name the data is stored under. One buyer hit this directly: their target was stored as "Acme Industrial" while everyone referred to it as "Acme Water," and a literal name search would have missed it.
Crustdata's company identification endpoint handles this with fuzzy matching, and it is free, so resolving names first costs nothing and protects the search credits you spend later:
curl -X POST 'https://api.crustdata.com/screener/identify' \ -H 'Authorization: Token $CRUSTDATA_API_KEY' \ -H 'Content-Type: application/json' \ -d '{"query_company_name": "Acme Water", "count": 5}'
curl -X POST 'https://api.crustdata.com/screener/identify' \ -H 'Authorization: Token $CRUSTDATA_API_KEY' \ -H 'Content-Type: application/json' \ -d '{"query_company_name": "Acme Water", "count": 5}'
curl -X POST 'https://api.crustdata.com/screener/identify' \ -H 'Authorization: Token $CRUSTDATA_API_KEY' \ -H 'Content-Type: application/json' \ -d '{"query_company_name": "Acme Water", "count": 5}'
The response returns matching companies best-match first, each with a company_id. You feed those IDs into the people search instead of raw names, which removes the guesswork about whether a company matched at all. For the industry-to-executives case, this step is also where you decompose an industry into its companies, resolve each one, and collect the IDs before any search runs.
Step 4: Search for people who match your criteria
When you want people who match a set of criteria, the search endpoint is the one to call. Reaching for enrichment instead is the most common early mistake, because enrichment only fills in detail about a person you already have, while search takes a set of criteria and returns the people who fit. The private-equity team above spent their first session calling enrichment when what they wanted was "putting in a company name and getting out people who meet certain criteria," which is search.
With company IDs resolved, the executive-finder query filters on employer, title, and tenure. The hero filter here is the title match across current and former roles, so you catch the executive who left eighteen months ago as well as the one still in the chair:
{ "filters": { "op": "and", "conditions": [ { "column": "all_employers.company_id", "type": "in", "value": [631480, 902145] }, { "op": "or", "conditions": [ { "column": "all_employers.title", "type": "[.]", "value": "Chief Executive Officer" }, { "column": "all_employers.title", "type": "[.]", "value": "Chief Financial Officer" }, { "column": "all_employers.title", "type": "[.]", "value": "Chief Operating Officer" } ]} ] }, "limit": 25 }
{ "filters": { "op": "and", "conditions": [ { "column": "all_employers.company_id", "type": "in", "value": [631480, 902145] }, { "op": "or", "conditions": [ { "column": "all_employers.title", "type": "[.]", "value": "Chief Executive Officer" }, { "column": "all_employers.title", "type": "[.]", "value": "Chief Financial Officer" }, { "column": "all_employers.title", "type": "[.]", "value": "Chief Operating Officer" } ]} ] }, "limit": 25 }
{ "filters": { "op": "and", "conditions": [ { "column": "all_employers.company_id", "type": "in", "value": [631480, 902145] }, { "op": "or", "conditions": [ { "column": "all_employers.title", "type": "[.]", "value": "Chief Executive Officer" }, { "column": "all_employers.title", "type": "[.]", "value": "Chief Financial Officer" }, { "column": "all_employers.title", "type": "[.]", "value": "Chief Operating Officer" } ]} ] }, "limit": 25 }
The all_employers prefix searches both current and past positions, which is what "held the title in the last five years" requires. The nested op: "or" block matches any of the three titles. To narrow to recent tenure, add a condition on past_employers.end_date greater than your five-year cutoff, which keeps recent formers and drops people who left a decade ago. Run this once per resolved company, or pass several IDs in the in array to search them together.
Step 5: Enrich and rank the matches
Search gives you the people, and the same call can give you their contact details and career history if you ask for the right fields. Rather than a second enrichment round trip, request the fields you need directly in the search, and the response comes back enriched:
{ "filters": { "op": "and", "conditions": [ "...your filters..." ] }, "fields": "name,current_employers.title,current_employers.name,location_country,business_emails,past_employers.title,past_employers.name,education_background.institute_name", "limit": 25 }
{ "filters": { "op": "and", "conditions": [ "...your filters..." ] }, "fields": "name,current_employers.title,current_employers.name,location_country,business_emails,past_employers.title,past_employers.name,education_background.institute_name", "limit": 25 }
{ "filters": { "op": "and", "conditions": [ "...your filters..." ] }, "fields": "name,current_employers.title,current_employers.name,location_country,business_emails,past_employers.title,past_employers.name,education_background.institute_name", "limit": 25 }
Requesting business_emails returns verified business email, and the employer and education fields give you the work history you need to judge fit. From there, ranking is a prompt: ask Cursor to score each person against the brief in your rules file, putting current officers and the most recent formers at the top. For the executive search, the buyer's goal was a tight list of five to ten people per company, each with enough detail to brief a partner, which is exactly what a ranked, enriched result produces. One automation engineer running this kind of search described the output as "a targeted, fully targeted precision list" of exactly the people they wanted, with full contact detail attached.
If you need deeper one-pagers (recent news, web presence, career narrative), layer a people enrichment call on the top-ranked names, and feed that context back into Cursor to draft the summary. Finding the right people is the hard part, and the pipeline has now done it.
Step 6: Get full coverage (and verify it yourself)
Coverage is the thing that decides whether a people-data provider is good enough, so build the pipeline to maximize recall and then measure it. A query that returns half the real population looks like it works right up until you notice the names that are missing. Three habits raise recall:
Search current and former roles together: the
all_employersfilter catches executives who have already moved on, which a current-only search silently drops.Expand title variants: "CEO," "Chief Executive Officer," and "Founder & CEO" are the same person to you and different strings to the database, so match on several.
Resolve every company first: an unresolved name returns nobody, and you will not always notice the gap.
To check coverage honestly, run the same criteria against two providers and compare the overlap of names returned. One team did exactly this in Cursor, pointing the same query at two people-data APIs and measuring how many names each surfaced that the other missed. The overlap was lower than they expected, which is the point: you cannot assume any single source is complete, and the only way to know is to measure it on your own target list. Because the pipeline is provider-agnostic, running this test only swaps the endpoint. The search itself does not change.
Step 7: Keep runs clean and export
When you rerun a search, drop the people you have already pulled so each run returns only new matches. Crustdata's search supports post-processing exclusions for this. The simplest is to exclude by name:
{ "filters": { "op": "and", "conditions": [ "...your filters..." ] }, "post_processing": { "exclude_names": ["Jane Okafor", "Daniel Reyes"] }, "limit": 25 }
{ "filters": { "op": "and", "conditions": [ "...your filters..." ] }, "post_processing": { "exclude_names": ["Jane Okafor", "Daniel Reyes"] }, "limit": 25 }
{ "filters": { "op": "and", "conditions": [ "...your filters..." ] }, "post_processing": { "exclude_names": ["Jane Okafor", "Daniel Reyes"] }, "limit": 25 }
You can also exclude by profile URL using exclude_profiles with the URLs returned from previous runs, which is the precise way to de-duplicate across paginated calls or skip people already in your outreach. Keeping a running exclusion list is what turns a one-off search into a pipeline you can run weekly without re-reviewing the same faces.
The final stage writes the result somewhere your team uses. Ask Cursor to write the ranked list to a CSV, or to push it straight into your CRM or applicant tracking system through that system's API. The export needs a field mapping so the data lands in the right columns:
Pipeline field | API field | CRM / ATS column |
|---|---|---|
Full name |
| Full Name |
Current title |
| Title |
Current company |
| Company |
Location |
| Country |
Verified email |
| |
Past roles |
| Experience / Notes |
With the export wired up, the whole pipeline runs end to end: resolve companies, search people, enrich and rank, exclude the ones you have seen, and write the shortlist to where it gets used.
One pipeline, repointed
The same pipeline serves more than one job because only the criteria file changes between them. A solo builder we spoke with ran the identical setup in Cursor for two reasons: "trying to find the right person at a company" for prospecting, and, in their words, "the other is for hiring." The search that finds board-ready executives becomes a sales-prospecting search when the rules file targets VPs of Engineering at fast-growing companies, and a recruiting search when it targets senior engineers with specific tenure and skills.
The data provider is just as swappable. Because the pipeline is a thin layer of search, enrich, filter, and export, moving from one people-data API to another means re-pointing the request and adjusting field names. The rest of the workflow stays as it is. That is the property the private-equity team valued most: if a provider returns the coverage they need, they "will just swap the data source" and keep everything else. Building the pipeline in Cursor is what makes that swap a small edit rather than a project.
What breaks: cost, coverage, and judgment
A few things will bite if you ignore them, so plan for them up front.
Cost discipline at production scale: running broad, unfiltered searches across an entire industry adds up fast, and a per-query approach that is fine for a prototype can become expensive in production. The fix is the pipeline itself: resolve companies first with the free endpoint, narrow your filters before you spend search credits, and batch resolved IDs into single searches. For production volume pricing on people search, talk to a Crustdata solutions engineer, who can size it to your actual query pattern.
Coverage gaps: no single provider has everyone, which is why Step 6 builds the overlap check into your process rather than treating any one source as complete.
Data propagation lag: when someone changes jobs, it takes time for that to appear across data sources, so a recent mover may still show an old title. Searching current and former roles together reduces how often this matters.
Human judgment stays in the loop: the pipeline produces a ranked shortlist, and a person still reviews the top names before anyone reaches out. That review matters both because the stakes are high for board and executive searches and because automated outreach without it reads as exactly that. Crustdata is built to be the data layer for teams building these search pipelines, giving you the people and the detail so your judgment goes into the criteria and the final pick.
Conclusion
A people search pipeline in Cursor changes what it costs to go from a question ("who has run a company like this one?") to an answer (a ranked, contactable shortlist). You describe the people you want, the editor writes the integration, and the same loop serves executive search, sales prospecting, and recruiting depending on the criteria you point it at. The build is small enough that swapping data providers or adjusting the brief is a small edit rather than a rewrite.
The code was always the easy part, and the hard part is getting a data source that returns the right people with enough coverage to trust. If you want to see whether Crustdata's people search returns the coverage your target list needs, and walk through wiring it into Cursor with an engineer, book a demo.
Frequently asked questions
Why Cursor instead of Claude Code?
Both work, and the pipeline is identical either way. Cursor is an AI code editor many builders already use day to day, so the people search lives in the same tool as the rest of their code. Claude Code is a terminal-based agent that runs the same MCP servers and the same API calls, and we cover that variant in our guide to building a B2B prospecting workflow with Claude Code. Choose whichever you already build in.
Do I need to know how to code to build this?
You need enough to run a script and store an API key, but Cursor writes most of the integration from your description. The skill that matters more is describing the people you want precisely: the right titles, the recency window, and the target companies. That brief is what makes the search accurate.
Should I use the MCP server or call the API directly?
Use the MCP server to start, because Cursor handles the request construction and pagination for you. Call the API directly when you want to wrap the search in your own scripts or control exactly how requests are built. Both hit the same data, so you can begin with MCP and move to direct calls later.
Can the same pipeline do recruiting and sales prospecting?
Yes. The search, enrich, filter, and export stages stay the same, and only the criteria file changes. Point it at senior engineers for hiring, at decision-makers for prospecting, or at former executives for board search. One definition file per use case is all the difference between them.
How do I search for executives who have already left a company?
Filter on all_employers rather than current_employers, which searches both current and past positions. To keep the list recent, add a condition on past_employers.end_date after your cutoff date, so you catch executives who left in the last few years and drop those who moved on long ago.
How do I check a provider's coverage before committing?
Run your real target criteria against two providers and compare the names each returns. Measure how many each surfaces that the other misses, on your own list rather than a generic sample. Because the pipeline is provider-agnostic, this test only swaps the endpoint and reuses the same search.
Products
Popular Use Cases
Competitor Comparisons
Use Cases
95 Third Street, 2nd Floor, San Francisco,
California 94103, United States of America
© 2026 Crustdata Inc.
Products
Popular Use Cases
Competitor Comparisons
Use Cases
95 Third Street, 2nd Floor, San Francisco,
California 94103, United States of America
© 2025 CrustData Inc.
Products
Popular Use Cases
Competitor Comparisons
Use Cases
95 Third Street, 2nd Floor, San Francisco,
California 94103, United States of America
© 2026 Crustdata Inc.


