Find decision-makers at a specific company using Apollo, Crustdata, Fiber, and PDL people search via Gooseworks MCP. Given a company name and target titles, returns a list of contacts with name, title, LinkedIn URL, and location.
npx gooseworks install --all # then, in Claude Code, Cursor, or Codex: /gooseworks use the company-contact-finder skill
Find decision-makers at a specific company by name and target titles. Uses Gooseworks MCP tools (Apollo, Crustdata, Fiber, PDL) with a layered fallback strategy to maximize results while minimizing cost.
| Input | Required | Default | Description |
|---|---|---|---|
| company_name | Yes | -- | The company to search (e.g., "EisnerAmper") |
| company_linkedin_url | No | -- | Company LinkedIn URL for disambiguation |
| target_titles | Yes | -- | List of titles to find (e.g., ["Partner", "Controller", "VP Finance"]) |
| num_results | No | 10 | How many contacts to return |
Parse the user's request to extract:
If the user does not provide target titles, ask for them. Suggest common senior titles based on context:
Apollo is the cheapest search provider. Start here for all searches.
Call:
apollo_person_search(
person_titles: ["Partner", "Controller", "VP Finance"],
organization_domains: ["eisneramper.com"],
per_page: 25
)If you don't have the company domain, use q_keywords with the company name:
apollo_person_search(
person_titles: ["Partner", "Controller", "VP Finance"],
q_keywords: "EisnerAmper",
per_page: 25
)Parse the response: Each result contains: name, title, company, LinkedIn URL, location, email, and other profile fields. Extract and collect all results into a working list.
Check how many results from Step 2 match the target titles at the target company.
Quality checks:
Decision:
Fiber supports natural-language queries and may have profiles Apollo does not.
Call:
fiber_person_search(
query: "[title1] OR [title2] OR [title3] at [company_name]",
page_size: 25
)After results return:
Decision:
Use Crustdata's structured filter search for more precise matching. Run one search per target title, then merge results.
For each target title, call:
crustdata_person_search(
conditions: [
{"column": "current_employers.name", "type": "in", "value": "[company_name]"},
{"column": "current_employers.title", "type": "(.)", "value": "[target_title]"}
],
filter_op: "and",
limit: 25
)Example for "Partner" at EisnerAmper:
crustdata_person_search(
conditions: [
{"column": "current_employers.name", "type": "=", "value": "EisnerAmper"},
{"column": "current_employers.title", "type": "(.)", "value": "Partner"}
],
filter_op: "and",
limit: 25
)Optional seniority filter: If the user requests senior decision-makers broadly (rather than specific titles), add:
{"column": "current_employers.seniority_level", "type": "in", "value": "VP,C-Level,Director"}TIP: Use preview: true first to check result count for free before fetching full data.
After all title searches complete:
Decision:
PeopleDataLabs is the most expensive search provider. Only use as a last resort when other sources have insufficient results.
Call:
pdl_person_search(
job_titles: ["Partner", "Controller", "VP Finance"],
company_names: ["EisnerAmper"],
num_results: 10
)After results return:
Present the final deduplicated contact list.
Table format (for the user):
| # | Name | Title | Company | LinkedIn URL | Location |
|---|---|---|---|---|---|
| 1 | Jane Smith | Partner | EisnerAmper | https://linkedin.com/in/janesmith | New York, NY |
| 2 | John Doe | Controller | EisnerAmper | https://linkedin.com/in/johndoe | Chicago, IL |
| ... |
JSON format (for downstream skills):
{
"company": "EisnerAmper",
"search_titles": ["Partner", "Controller", "VP Finance"],
"contacts": [
{
"name": "Jane Smith",
"title": "Partner",
"company": "EisnerAmper",
"linkedin_url": "https://linkedin.com/in/janesmith",
"location": "New York, NY"
}
],
"total_found": 10,
"sources": ["apollo", "fiber", "crustdata", "pdl"]
}Summary line:
Found X contacts matching [titles] at [company]. Sources used: [list of sources that returned results].
If fewer than 3 contacts were found after all fallbacks, tell the user:
Only found X contacts. The company may be small, the titles may be uncommon, or the databases may have limited coverage for this company. Consider broadening the target titles or trying alternate company name spellings.
| Provider | Cost | Tool |
|---|---|---|
| Apollo | $0.01 flat | apollo_person_search |
| Fiber | $0.50 | fiber_person_search |
| Crustdata | $0.66/page | crustdata_person_search |
| PDL | $7.50 | pdl_person_search |
Always start with Apollo. Escalate through Fiber → Crustdata → PDL only as fallbacks.
| Tool | Purpose | Key Params |
|---|---|---|
apollo_person_search | People search by title, location, company ($0.01/call) | person_titles, person_locations, organization_domains, q_keywords, per_page |
fiber_person_search | NL or structured people search ($0.02/record) | query (NL), search_params (structured), page_size |
crustdata_person_search | Structured filter search ($0.66/page of 100) | conditions, filter_op, limit, preview |
pdl_person_search | PDL people search ($0.30/record — last resort) | job_titles, company_names, location_country, num_results |
fetch_linkedin_profile | Enrich a single person by LinkedIn URL | linkedin_url |
| Column | Operators | Example Values |
|---|---|---|
current_employers.name | = (exact), (.) (contains) | "EisnerAmper" |
current_employers.title | (.) (fuzzy), = (exact) | "Partner" |
current_employers.seniority_level | =, (.) | "VP", "C-Level" |
region | = | "San Francisco" |
skills | (.) | "python" |
Find Partners and Controllers at EisnerAmperAgent calls apollo_person_search with person_titles: ["Partner", "Controller"], q_keywords: "EisnerAmper", per_page: 25.
Find VP Finance and CFO at companies using Sage IntacctAgent builds query: "VP Finance OR CFO at Sage Intacct".
Find Managing Directors at CPA firms in San FranciscoAgent builds query: "Managing Director at CPA firm San Francisco".
Find Partners at EisnerAmper (https://linkedin.com/company/eisneramper)Agent uses the company name "EisnerAmper" and can use the LinkedIn URL for enrichment if needed.
The Gooseworks MCP tools require the Gooseworks MCP server to be configured in your environment. If you get errors like "tool not found" or connection failures:
claude_desktop_config.json or equivalent).(.) operatorin operator for exact title matching instead of fuzzy (.)The skill deduplicates by LinkedIn URL automatically. If you see near-duplicates with slightly different URLs (e.g., trailing slashes), normalize URLs before deduplication by stripping trailing slashes and query parameters.
metadata:
requires:
mcp_servers: ["gooseworks"]
cost: "From $0.01 (Apollo) to $7.50 (PDL) depending on provider and result count"Render a 'model comparison grid' video from a config — a fal-style "same prompt, N contenders" showcase — a dark real-DOM stage where per beat a monospace prompt fades in centered, docks to a small top strip, then a labeled 2-4 panel grid (static images OR muted video clips, mixable per cell) staggers in and holds for comparison, plus a minimal end card — frame-stepped via Playwright (video cells are frame-seeked deterministically) and encoded with FFmpeg. Deterministic assembly, FREE (cell media comes from create-image-fal / create-video-fal, music from create-music-elevenlabs), text stays pixel-crisp. Use for the model-comparison-grid format.
Render a punchy ~12s vertical (9:16) music-only direct-response OFFER ad as a 4-beat kinetic-typography film — HEADLINE slam → real PRODUCT drop → CLAIM/proof → CTA pill — from one config of copy slots, a real product photo, a brand palette, fonts, bpm, and beat split. DETERMINISTIC + FREE (a bundled Remotion project; springs + interpolate, no AI-gen for visuals). Backgrounds are engine gradient divs off the palette, props are inline SVG, the ONLY composited bitmap is the REAL product photo (objectFit:contain, never stretched), and ALL headline/claim/CTA/URL/wordmark text is typeset in the engine — never AI-rendered (the format's credibility guard). A driver binds the config to Remotion input props, renders the 9:16 master, and derives a 1:1 center-crop with ffmpeg. Two gating checks run before render (claim verbs must match the product's physical format; the claim beat needs an edge-entry mechanism prop). Use for the motion-graphics-offer-ad format.
Assemble a myth-vs-fact kinetic-typography explainer video ad (≈29.5s, 9:16) from N myth/fact pairs + hook / turn / punch copy + palette + a brand end-card PNG + a VO track — a hook, 3 red-strike MYTH cards that flip to teal-check FACT cards (per-line strikethrough that crosses EVERY wrapped line), a "what actually works" turn, an optional proof reveal, a punch line, and a static end card. DETERMINISTIC assembly with ZERO AI-gen visuals — HTML hyperframes rendered frame-exact via Playwright (`window.renderAt(t)`, animation a pure function of beat-local time), Whisper beat-snap to VO word onsets, concat at a uniform fps, karaoke `.ass` captions burned last (suppressed on the proof + end-card beats), and a VO + optional music mix (music −20 dB, `amix normalize=0`, tail fade). FREE (Python + Playwright + ffmpeg); the recipe supplies the copy / palette / end-card / VO and gates the paid VO / music / Whisper calls to their own capabilities. Use for the myth-vs-fact format.