Prospeo full export skill

Export your entire Prospeo people search to CSV.

by growthenginenowoslawski·MIT license·★ 736 Stars on the repo·GitHub ↗

Use now

Files of Prospeo full export

growthenginenowoslawski/main1 file shown
SKILL.md
Show the full text546 lines

Prospeo Full Search Export

Extract your entire Prospeo people search to a CSV file. Build your search in Prospeo's UI, then let Claude pull every single result via the API — even if the search has more than 25,000 results.

Required step: Qualify with /icp-prompt-builder (do not skip)

Before exporting more than 500 contacts, run Prospeo on a 50-contact sample, then invoke /icp-prompt-builder to tune a qualification prompt in 3-5 rounds of 10 (with your approval each round). Apply the tuned prompt to the full export to filter out bad fits.

Why required: email enrichment downstream costs $0.05-$0.15 per person. A 25K export that's 40% wrong-fit wastes $500-$1,500 on email-finding that goes nowhere. The ICP prompt builder takes 10-15 min and saves that cost 40-70% of the time.

Safe skip: only if your Prospeo filter is already extremely tight (e.g., 5 exact titles + 1 industry + narrow headcount) AND you've run the same filter successfully before. Even then, run /icp-prompt-builder on 10 samples as a sanity check — it's nearly free to confirm.

What This Does

  1. You build a search in Prospeo's web UI (filters for title, location, industry, company size, etc.)
  2. You tell Claude what filters you used
  3. Claude translates those filters into Prospeo API calls
  4. Claude paginates through every page of results and exports to CSV
  5. For large US searches (25K+), Claude automatically splits by state to get everything

Setup (First Time Only)

Step 1: Create a Prospeo Account
  1. Go to prospeo.io and sign up
  2. Choose a plan that includes the Search Person API (most paid plans do)
  3. Each API request that returns results costs 1 credit and returns 25 contacts
Step 2: Get Your API Key
  1. Log into Prospeo
  2. Go to Settings > API (or visit prospeo.io/app/settings/api)
  3. Copy your API key
Step 3: Set Your API Key

Set it as an environment variable so Claude can use it:

# Add to your shell profile (~/.zshrc or ~/.bashrc)
export PROSPEO_API_KEY="your_api_key_here"

Then restart your terminal or run source ~/.zshrc.

Security note: Never paste your API key directly into a script file. Always use environment variables.


How to Use

Step 1: Build Your Search in Prospeo's UI

Go to prospeo.io/app/search and use the filters to build your search. The UI lets you filter by:

  • Job title (e.g., "CEO", "VP Sales", "Head of Marketing")
  • Location (e.g., "United States", "California", "New York")
  • Company industry (e.g., "Information Technology", "Healthcare")
  • Company headcount (e.g., 11-500 employees)
  • Company technology (e.g., "Salesforce", "HubSpot")
  • Revenue range
  • Contact details (has verified email, has phone number)

Note the total result count shown in the UI — you'll need this to estimate credits.

Step 2: Tell Claude Your Filters

Just describe what you filtered for. Examples:

"I searched for CEOs and CTOs at companies with 11-500 employees in the US, in the Information Technology industry, with verified emails."

"I'm looking for VP of Sales and Head of Sales at SaaS companies in California with 50-200 employees."

"I need all Marketing Directors in the US at companies using HubSpot, 20-1000 headcount."

Step 3: Claude Runs the Export

Claude will:

  1. Confirm the filters and estimated credit cost
  2. Create a TypeScript script
  3. Run it to paginate through all results
  4. Export everything to a CSV file in your current directory

Filter Reference

These are the exact filter names the Prospeo API accepts. When you describe your search, Claude maps your description to these:

UI Filter API Filter Key Format
Job Title person_job_title { include: ["CEO", "CTO"], exclude: ["Intern"] }
Location person_location_search { include: ["California, United States #US"] }
Industry company_industry { include: ["Information Technology"] }
Headcount company_headcount_custom { min: 11, max: 500 }
Technology company_technology { include: ["Salesforce", "HubSpot"] }
Revenue company_revenue_custom { min: 1000000, max: 50000000 }
Founded Year company_founding_year { min: 2010, max: 2025 }
Company Name company_name { include: ["Acme"], exclude: ["Test"] }
Company Domain company_domain { include: ["acme.com"] }
Has Email person_contact_details { email: ["VERIFIED"] }
Has Phone person_contact_details { mobile: ["TRUE"] }
Exact Title Match person_job_title { include: [...], match_only_exact_job_titles: true }
Location Format

Locations must follow this exact format:

  • Country: "United States #US", "United Kingdom #GB", "Canada #CA"
  • State: "California, United States #US", "Texas, United States #US"
  • City: "San Francisco, California, United States #US"
Headcount Ranges (Common Presets)
Label min max
1-10 1 10
11-50 11 50
51-200 51 200
201-500 201 500
501-1000 501 1000
1001-5000 1001 5000
5001-10000 5001 10000
10001+ 10001 (omit max)

API Details

Endpoint: POST https://api.prospeo.io/search-person

Auth: X-KEY header with your API key

Rate Limit: 2 requests per second (the script handles this automatically)

Pagination: 25 results per page, max 1000 pages = 25,000 results per search

Credits: 1 credit per request that returns at least 1 result. A 25,000-result search costs ~1,000 credits.

Request Format
const response = await fetch('https://api.prospeo.io/search-person', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'X-KEY': process.env.PROSPEO_API_KEY!,
  },
  body: JSON.stringify({
    page: 1,  // 1-1000
    filters: {
      person_job_title: { include: ['CEO', 'CTO'] },
      person_location_search: { include: ['United States #US'] },
      company_headcount_custom: { min: 11, max: 500 },
      company_industry: { include: ['Information Technology'] },
      person_contact_details: { email: ['VERIFIED'] },
    },
  }),
});
Response Format
{
  error: false,
  results: [
    {
      person: {
        person_id: "abc123",
        first_name: "Jane",
        last_name: "Smith",
        full_name: "Jane Smith",
        current_job_title: "CEO",
        linkedin_url: "https://linkedin.com/in/janesmith",
        email: "[email protected]",
        email_status: "VERIFIED",
        phone: "+14155551234",
        location: { city: "San Francisco", state: "California", country: "United States" }
      },
      company: {
        name: "Acme Corp",
        domain: "acme.com",
        linkedin_url: "https://linkedin.com/company/acme",
        industry: "Information Technology",
        headcount: 150,
        headcount_range: "51-200",
        technologies: ["Salesforce", "HubSpot"],
        location: { city: "San Francisco", state: "California", country: "United States" }
      }
    }
  ],
  pagination: {
    current_page: 1,
    total_page: 400,
    total_count: 10000,
    per_page: 25
  }
}

The 25K Limit: State-by-State Splitting

Prospeo caps any single search at 25,000 results (1,000 pages x 25 per page). If your US-wide search has more than 25K results, the script automatically splits it into 50 separate state-level searches.

How it works:

  1. Run the search once to check total_count
  2. If > 20,000, switch to state-by-state mode
  3. Replace "United States #US" with each state (e.g., "California, United States #US")
  4. Paginate through each state's results
  5. Deduplicate across states (by LinkedIn URL)

This means you can extract hundreds of thousands of results from a single search definition.

US States (ordered by population for efficiency)
California, Texas, Florida, New York, Illinois, Pennsylvania,
Ohio, Georgia, North Carolina, Michigan, New Jersey, Virginia,
Washington, Arizona, Massachusetts, Tennessee, Indiana, Missouri,
Maryland, Wisconsin, Colorado, Minnesota, South Carolina, Alabama,
Louisiana, Kentucky, Oregon, Oklahoma, Connecticut, Utah, Iowa,
Nevada, Arkansas, Mississippi, Kansas, New Mexico, Nebraska,
Idaho, West Virginia, Hawaii, New Hampshire, Maine, Montana,
Rhode Island, Delaware, South Dakota, North Dakota, Alaska,
Vermont, Wyoming

Script Template

When Claude generates the export script, it follows this pattern:

import { writeFileSync } from 'fs';

// --- Config ---
const API_KEY = process.env.PROSPEO_API_KEY;
if (!API_KEY) {
  console.error('Set PROSPEO_API_KEY environment variable first.');
  process.exit(1);
}

const RATE_LIMIT_MS = 500; // 2 requests/sec
const MAX_RETRIES = 5;

// --- Types ---
interface ProspeoFilters {
  person_job_title?: { include?: string[]; exclude?: string[]; match_only_exact_job_titles?: boolean };
  person_location_search?: { include?: string[]; exclude?: string[] };
  company_headcount_custom?: { min?: number; max?: number };
  company_industry?: { include?: string[]; exclude?: string[] };
  company_technology?: { include?: string[]; exclude?: string[] };
  company_revenue_custom?: { min?: number; max?: number };
  company_founding_year?: { min?: number; max?: number };
  company_name?: { include?: string[]; exclude?: string[] };
  company_domain?: { include?: string[]; exclude?: string[] };
  person_contact_details?: { email?: string[]; mobile?: string[]; operator?: string };
}

// --- Rate-limited fetch ---
const sleep = (ms: number) => new Promise(r => setTimeout(r, ms));

async function searchPage(filters: ProspeoFilters, page: number, retries = 0): Promise<any> {
  await sleep(RATE_LIMIT_MS);

  const res = await fetch('https://api.prospeo.io/search-person', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json', 'X-KEY': API_KEY! },
    body: JSON.stringify({ page, filters }),
  });

  if (res.status === 429 && retries < MAX_RETRIES) {
    const backoff = Math.min(2000 * Math.pow(2, retries), 60000);
    console.log(`  Rate limited, waiting ${backoff / 1000}s...`);
    await sleep(backoff);
    return searchPage(filters, page, retries + 1);
  }

  if (!res.ok) throw new Error(`API error: ${res.status} ${res.statusText}`);
  return res.json();
}

// --- Deduplication ---
const seenLinkedIn = new Set<string>();
const seenEmail = new Set<string>();

function isDuplicate(person: any): boolean {
  const li = person.linkedin_url;
  const em = person.email;
  if (li && seenLinkedIn.has(li)) return true;
  if (em && seenEmail.has(em)) return true;
  if (li) seenLinkedIn.add(li);
  if (em) seenEmail.add(em);
  return false;
}

// --- CSV helpers ---
function escapeCSV(val: any): string {
  if (val == null) return '';
  const s = String(val);
  return s.includes(',') || s.includes('"') || s.includes('\n')
    ? `"${s.replace(/"/g, '""')}"` : s;
}

function resultToRow(r: any): string[] {
  const p = r.person || {};
  const c = r.company || {};
  return [
    p.first_name, p.last_name, p.full_name, p.current_job_title,
    p.email, p.email_status, p.phone, p.linkedin_url,
    p.location?.city, p.location?.state, p.location?.country,
    c.name, c.domain, c.linkedin_url, c.industry,
    c.headcount, c.headcount_range,
    (c.technologies || []).join('; '),
    c.location?.city, c.location?.state, c.location?.country,
  ];
}

const CSV_HEADERS = [
  'first_name', 'last_name', 'full_name', 'job_title',
  'email', 'email_status', 'phone', 'linkedin_url',
  'person_city', 'person_state', 'person_country',
  'company_name', 'company_domain', 'company_linkedin', 'company_industry',
  'company_headcount', 'company_headcount_range',
  'company_technologies',
  'company_city', 'company_state', 'company_country',
];

// --- Main export ---
async function exportSearch(filters: ProspeoFilters, outputFile: string, maxResults?: number) {
  console.log('Running initial search to check result count...');
  const first = await searchPage(filters, 1);

  if (first.error) {
    console.error('API error:', first.message);
    process.exit(1);
  }

  const totalCount = first.pagination?.total_count || 0;
  const totalPages = first.pagination?.total_page || 0;
  console.log(`Found ${totalCount.toLocaleString()} total results (${totalPages} pages)`);

  // Check if we need state-by-state splitting
  const needsSplit = totalCount > 20000
    && filters.person_location_search?.include?.some(l => l === 'United States #US');

  if (needsSplit) {
    console.log('Search exceeds 20K — switching to state-by-state mode...');
    await exportByState(filters, outputFile, maxResults);
    return;
  }

  // Simple pagination
  const rows: string[][] = [];
  const pagesToFetch = maxResults ? Math.min(Math.ceil(maxResults / 25), totalPages) : totalPages;
  const creditsEstimate = pagesToFetch;
  console.log(`Will fetch ${pagesToFetch} pages (~${creditsEstimate} credits)`);

  // Process page 1 results we already have
  for (const r of first.results || []) {
    if (!isDuplicate(r.person)) rows.push(resultToRow(r));
  }
  console.log(`  Page 1/${pagesToFetch} — ${rows.length} contacts`);

  for (let page = 2; page <= pagesToFetch; page++) {
    if (maxResults && rows.length >= maxResults) break;
    const data = await searchPage(filters, page);
    for (const r of data.results || []) {
      if (!isDuplicate(r.person)) rows.push(resultToRow(r));
    }
    if (page % 50 === 0 || page === pagesToFetch) {
      console.log(`  Page ${page}/${pagesToFetch} — ${rows.length} contacts so far`);
    }
  }

  writeCSV(outputFile, rows);
}

// --- State-by-state export ---
const US_STATES = [
  'California', 'Texas', 'Florida', 'New York', 'Illinois', 'Pennsylvania',
  'Ohio', 'Georgia', 'North Carolina', 'Michigan', 'New Jersey', 'Virginia',
  'Washington', 'Arizona', 'Massachusetts', 'Tennessee', 'Indiana', 'Missouri',
  'Maryland', 'Wisconsin', 'Colorado', 'Minnesota', 'South Carolina', 'Alabama',
  'Louisiana', 'Kentucky', 'Oregon', 'Oklahoma', 'Connecticut', 'Utah', 'Iowa',
  'Nevada', 'Arkansas', 'Mississippi', 'Kansas', 'New Mexico', 'Nebraska',
  'Idaho', 'West Virginia', 'Hawaii', 'New Hampshire', 'Maine', 'Montana',
  'Rhode Island', 'Delaware', 'South Dakota', 'North Dakota', 'Alaska',
  'Vermont', 'Wyoming',
];

async function exportByState(filters: ProspeoFilters, outputFile: string, maxResults?: number) {
  const rows: string[][] = [];

  for (let i = 0; i < US_STATES.length; i++) {
    if (maxResults && rows.length >= maxResults) break;

    const state = US_STATES[i];
    const stateFilters = JSON.parse(JSON.stringify(filters));
    stateFilters.person_location_search.include =
      stateFilters.person_location_search.include.map((loc: string) =>
        loc === 'United States #US' ? `${state}, United States #US` : loc
      );

    const first = await searchPage(stateFilters, 1);
    const stateTotal = first.pagination?.total_count || 0;
    const statePages = first.pagination?.total_page || 0;

    if (stateTotal === 0) {
      console.log(`  [${i + 1}/50] ${state}: 0 results, skipping`);
      continue;
    }

    // Process page 1
    for (const r of first.results || []) {
      if (!isDuplicate(r.person)) rows.push(resultToRow(r));
    }

    // Paginate remaining
    for (let page = 2; page <= statePages; page++) {
      if (maxResults && rows.length >= maxResults) break;
      const data = await searchPage(stateFilters, page);
      for (const r of data.results || []) {
        if (!isDuplicate(r.person)) rows.push(resultToRow(r));
      }
    }

    console.log(`  [${i + 1}/50] ${state}: ${stateTotal.toLocaleString()} results — ${rows.length.toLocaleString()} total contacts`);
  }

  writeCSV(outputFile, rows);
}

// --- Write CSV ---
function writeCSV(outputFile: string, rows: string[][]) {
  const lines = [CSV_HEADERS.join(',')];
  for (const row of rows) {
    lines.push(row.map(escapeCSV).join(','));
  }
  writeFileSync(outputFile, lines.join('\n'), 'utf-8');
  console.log(`\nExport complete!`);
  console.log(`  File: ${outputFile}`);
  console.log(`  Contacts: ${rows.length.toLocaleString()}`);
  console.log(`  Credits used: ~${seenLinkedIn.size + seenEmail.size > 0 ? 'see above' : rows.length / 25}`);
}

// --- Entry point ---
// Claude will fill in the filters based on your search description
const filters: ProspeoFilters = {
  // FILTERS_GO_HERE
};

const outputFile = 'prospeo-export.csv';
exportSearch(filters, outputFile);

Credit Cost Estimation

Before running, Claude will estimate the credit cost:

Total Results Pages Credits Approximate Cost (varies by plan)
1,000 40 40 ~$2
5,000 200 200 ~$10
25,000 1,000 1,000 ~$50
100,000 (state split) ~4,000 ~4,000 ~$200

Claude will always tell you the estimated cost and ask for confirmation before running the full export.


Example Conversations

Simple search:

"Export all CEOs at 11-50 person companies in California in the SaaS industry with verified emails."

Large US-wide search:

"I need every VP of Sales and Head of Sales in the US at companies with 50-500 employees. The Prospeo UI shows 87,000 results."

With exclusions:

"Marketing Directors in the US, exclude staffing and recruiting industries, 20-200 headcount, must have verified email."

With technology filter:

"CTOs at companies using Shopify in the US, any company size."


Troubleshooting

"Set PROSPEO_API_KEY environment variable first"

Your API key isn't set. Run: export PROSPEO_API_KEY="your_key" in your terminal.

"API error: 401"

Your API key is invalid. Check it at prospeo.io/app/settings/api.

"API error: 402"

You're out of credits. Top up your Prospeo account.

"API error: 429"

Rate limited. The script handles this automatically with exponential backoff. If it persists, you're making too many concurrent requests — only run one export at a time.

Results seem low
  • Check that your location format is correct (must include #US, #GB, etc.)
  • Broaden your title filters — Prospeo does fuzzy matching by default
  • Remove the person_contact_details filter to see all results (not just those with verified emails)
Duplicates across states

The script deduplicates by LinkedIn URL automatically. Some contacts may appear in multiple state searches if they've relocated — the dedup handles this.


Requirements

  • Node.js 18+ (for native fetch support)
  • TypeScript (npm install -g tsx to run .ts files directly)
  • A Prospeo account with API credits

No other dependencies needed — the script uses only built-in Node.js modules.


What to do next

Run /icp-prompt-builder on a 50-contact sample (required step above). Apply the tuned prompt to your full export, then /list-quality-scorecard to grade.

Next: /campaign-copywriting → /smartlead-campaign-upload-public.

Or wait: if Prospeo returned <500 contacts for your filter, your ICP may be too narrow. Broaden titles (add synonyms) or industries before scaling.

  • /icp-prompt-builder — required qualification pass before scaling
  • /list-quality-scorecard — grade the filtered list
  • /campaign-copywriting — write the emails
  • /smartlead-campaign-upload-public — launch in DRAFT
1---
2name: prospeo-full-export
3description: Export your entire Prospeo people search to CSV. Build filters in Prospeo's UI, then use this skill to extract every result via the API — even searches over 25K. Handles pagination, rate limiting, deduplication, and state-by-state splitting automatically. Pair with /icp-prompt-builder on a 50-person sample to tune a qualification prompt BEFORE exporting 25K.
4---
5 
6# Prospeo Full Search Export
7 
8Extract your entire Prospeo people search to a CSV file. Build your search in Prospeo's UI, then let Claude pull every single result via the API — even if the search has more than 25,000 results.
9 
10## Required step: Qualify with /icp-prompt-builder (do not skip)
11 
12Before exporting more than 500 contacts, run Prospeo on a 50-contact sample, then invoke `/icp-prompt-builder` to tune a qualification prompt in 3-5 rounds of 10 (with your approval each round). Apply the tuned prompt to the full export to filter out bad fits.
13 
14**Why required:** email enrichment downstream costs $0.05-$0.15 per person. A 25K export that's 40% wrong-fit wastes $500-$1,500 on email-finding that goes nowhere. The ICP prompt builder takes 10-15 min and saves that cost 40-70% of the time.
15 
16**Safe skip:** only if your Prospeo filter is already extremely tight (e.g., 5 exact titles + 1 industry + narrow headcount) AND you've run the same filter successfully before. Even then, run `/icp-prompt-builder` on 10 samples as a sanity check — it's nearly free to confirm.
17 
18## What This Does
19 
201. You build a search in Prospeo's web UI (filters for title, location, industry, company size, etc.)
212. You tell Claude what filters you used
223. Claude translates those filters into Prospeo API calls
234. Claude paginates through every page of results and exports to CSV
245. For large US searches (25K+), Claude automatically splits by state to get everything
25 
26## Setup (First Time Only)
27 
28### Step 1: Create a Prospeo Account
29 
301. Go to [prospeo.io](https://prospeo.io) and sign up
312. Choose a plan that includes the **Search Person API** (most paid plans do)
323. Each API request that returns results costs **1 credit** and returns 25 contacts
33 
34### Step 2: Get Your API Key
35 
361. Log into Prospeo
372. Go to **Settings > API** (or visit [prospeo.io/app/settings/api](https://prospeo.io/app/settings/api))
383. Copy your API key
39 
40### Step 3: Set Your API Key
41 
42Set it as an environment variable so Claude can use it:
43 
44```bash
45# Add to your shell profile (~/.zshrc or ~/.bashrc)
46export PROSPEO_API_KEY="your_api_key_here"
47```
48 
49Then restart your terminal or run `source ~/.zshrc`.
50 
51**Security note:** Never paste your API key directly into a script file. Always use environment variables.
52 
53---
54 
55## How to Use
56 
57### Step 1: Build Your Search in Prospeo's UI
58 
59Go to [prospeo.io/app/search](https://prospeo.io/app/search) and use the filters to build your search. The UI lets you filter by:
60 
61- **Job title** (e.g., "CEO", "VP Sales", "Head of Marketing")
62- **Location** (e.g., "United States", "California", "New York")
63- **Company industry** (e.g., "Information Technology", "Healthcare")
64- **Company headcount** (e.g., 11-500 employees)
65- **Company technology** (e.g., "Salesforce", "HubSpot")
66- **Revenue range**
67- **Contact details** (has verified email, has phone number)
68 
69Note the total result count shown in the UI — you'll need this to estimate credits.
70 
71### Step 2: Tell Claude Your Filters
72 
73Just describe what you filtered for. Examples:
74 
75> "I searched for CEOs and CTOs at companies with 11-500 employees in the US, in the Information Technology industry, with verified emails."
76 
77> "I'm looking for VP of Sales and Head of Sales at SaaS companies in California with 50-200 employees."
78 
79> "I need all Marketing Directors in the US at companies using HubSpot, 20-1000 headcount."
80 
81### Step 3: Claude Runs the Export
82 
83Claude will:
841. Confirm the filters and estimated credit cost
852. Create a TypeScript script
863. Run it to paginate through all results
874. Export everything to a CSV file in your current directory
88 
89---
90 
91## Filter Reference
92 
93These are the exact filter names the Prospeo API accepts. When you describe your search, Claude maps your description to these:
94 
95| UI Filter | API Filter Key | Format |
96|-----------|---------------|--------|
97| Job Title | `person_job_title` | `{ include: ["CEO", "CTO"], exclude: ["Intern"] }` |
98| Location | `person_location_search` | `{ include: ["California, United States #US"] }` |
99| Industry | `company_industry` | `{ include: ["Information Technology"] }` |
100| Headcount | `company_headcount_custom` | `{ min: 11, max: 500 }` |
101| Technology | `company_technology` | `{ include: ["Salesforce", "HubSpot"] }` |
102| Revenue | `company_revenue_custom` | `{ min: 1000000, max: 50000000 }` |
103| Founded Year | `company_founding_year` | `{ min: 2010, max: 2025 }` |
104| Company Name | `company_name` | `{ include: ["Acme"], exclude: ["Test"] }` |
105| Company Domain | `company_domain` | `{ include: ["acme.com"] }` |
106| Has Email | `person_contact_details` | `{ email: ["VERIFIED"] }` |
107| Has Phone | `person_contact_details` | `{ mobile: ["TRUE"] }` |
108| Exact Title Match | `person_job_title` | `{ include: [...], match_only_exact_job_titles: true }` |
109 
110### Location Format
111 
112Locations must follow this exact format:
113- Country: `"United States #US"`, `"United Kingdom #GB"`, `"Canada #CA"`
114- State: `"California, United States #US"`, `"Texas, United States #US"`
115- City: `"San Francisco, California, United States #US"`
116 
117### Headcount Ranges (Common Presets)
118 
119| Label | min | max |
120|-------|-----|-----|
121| 1-10 | 1 | 10 |
122| 11-50 | 11 | 50 |
123| 51-200 | 51 | 200 |
124| 201-500 | 201 | 500 |
125| 501-1000 | 501 | 1000 |
126| 1001-5000 | 1001 | 5000 |
127| 5001-10000 | 5001 | 10000 |
128| 10001+ | 10001 | (omit max) |
129 
130---
131 
132## API Details
133 
134**Endpoint:** `POST https://api.prospeo.io/search-person`
135 
136**Auth:** `X-KEY` header with your API key
137 
138**Rate Limit:** 2 requests per second (the script handles this automatically)
139 
140**Pagination:** 25 results per page, max 1000 pages = **25,000 results per search**
141 
142**Credits:** 1 credit per request that returns at least 1 result. A 25,000-result search costs ~1,000 credits.
143 
144### Request Format
145 
146```typescript
147const response = await fetch('https://api.prospeo.io/search-person', {
148 method: 'POST',
149 headers: {
150 'Content-Type': 'application/json',
151 'X-KEY': process.env.PROSPEO_API_KEY!,
152 },
153 body: JSON.stringify({
154 page: 1, // 1-1000
155 filters: {
156 person_job_title: { include: ['CEO', 'CTO'] },
157 person_location_search: { include: ['United States #US'] },
158 company_headcount_custom: { min: 11, max: 500 },
159 company_industry: { include: ['Information Technology'] },
160 person_contact_details: { email: ['VERIFIED'] },
161 },
162 }),
163});
164```
165 
166### Response Format
167 
168```typescript
169{
170 error: false,
171 results: [
172 {
173 person: {
174 person_id: "abc123",
175 first_name: "Jane",
176 last_name: "Smith",
177 full_name: "Jane Smith",
178 current_job_title: "CEO",
179 linkedin_url: "https://linkedin.com/in/janesmith",
180 email: "[email protected]",
181 email_status: "VERIFIED",
182 phone: "+14155551234",
183 location: { city: "San Francisco", state: "California", country: "United States" }
184 },
185 company: {
186 name: "Acme Corp",
187 domain: "acme.com",
188 linkedin_url: "https://linkedin.com/company/acme",
189 industry: "Information Technology",
190 headcount: 150,
191 headcount_range: "51-200",
192 technologies: ["Salesforce", "HubSpot"],
193 location: { city: "San Francisco", state: "California", country: "United States" }
194 }
195 }
196 ],
197 pagination: {
198 current_page: 1,
199 total_page: 400,
200 total_count: 10000,
201 per_page: 25
202 }
203}
204```
205 
206---
207 
208## The 25K Limit: State-by-State Splitting
209 
210Prospeo caps any single search at 25,000 results (1,000 pages x 25 per page). If your US-wide search has more than 25K results, the script automatically splits it into 50 separate state-level searches.
211 
212**How it works:**
2131. Run the search once to check `total_count`
2142. If > 20,000, switch to state-by-state mode
2153. Replace `"United States #US"` with each state (e.g., `"California, United States #US"`)
2164. Paginate through each state's results
2175. Deduplicate across states (by LinkedIn URL)
218 
219This means you can extract **hundreds of thousands** of results from a single search definition.
220 
221### US States (ordered by population for efficiency)
222 
223```
224California, Texas, Florida, New York, Illinois, Pennsylvania,
225Ohio, Georgia, North Carolina, Michigan, New Jersey, Virginia,
226Washington, Arizona, Massachusetts, Tennessee, Indiana, Missouri,
227Maryland, Wisconsin, Colorado, Minnesota, South Carolina, Alabama,
228Louisiana, Kentucky, Oregon, Oklahoma, Connecticut, Utah, Iowa,
229Nevada, Arkansas, Mississippi, Kansas, New Mexico, Nebraska,
230Idaho, West Virginia, Hawaii, New Hampshire, Maine, Montana,
231Rhode Island, Delaware, South Dakota, North Dakota, Alaska,
232Vermont, Wyoming
233```
234 
235---
236 
237## Script Template
238 
239When Claude generates the export script, it follows this pattern:
240 
241```typescript
242import { writeFileSync } from 'fs';
243 
244// --- Config ---
245const API_KEY = process.env.PROSPEO_API_KEY;
246if (!API_KEY) {
247 console.error('Set PROSPEO_API_KEY environment variable first.');
248 process.exit(1);
249}
250 
251const RATE_LIMIT_MS = 500; // 2 requests/sec
252const MAX_RETRIES = 5;
253 
254// --- Types ---
255interface ProspeoFilters {
256 person_job_title?: { include?: string[]; exclude?: string[]; match_only_exact_job_titles?: boolean };
257 person_location_search?: { include?: string[]; exclude?: string[] };
258 company_headcount_custom?: { min?: number; max?: number };
259 company_industry?: { include?: string[]; exclude?: string[] };
260 company_technology?: { include?: string[]; exclude?: string[] };
261 company_revenue_custom?: { min?: number; max?: number };
262 company_founding_year?: { min?: number; max?: number };
263 company_name?: { include?: string[]; exclude?: string[] };
264 company_domain?: { include?: string[]; exclude?: string[] };
265 person_contact_details?: { email?: string[]; mobile?: string[]; operator?: string };
266}
267 
268// --- Rate-limited fetch ---
269const sleep = (ms: number) => new Promise(r => setTimeout(r, ms));
270 
271async function searchPage(filters: ProspeoFilters, page: number, retries = 0): Promise<any> {
272 await sleep(RATE_LIMIT_MS);
273 
274 const res = await fetch('https://api.prospeo.io/search-person', {
275 method: 'POST',
276 headers: { 'Content-Type': 'application/json', 'X-KEY': API_KEY! },
277 body: JSON.stringify({ page, filters }),
278 });
279 
280 if (res.status === 429 && retries < MAX_RETRIES) {
281 const backoff = Math.min(2000 * Math.pow(2, retries), 60000);
282 console.log(` Rate limited, waiting ${backoff / 1000}s...`);
283 await sleep(backoff);
284 return searchPage(filters, page, retries + 1);
285 }
286 
287 if (!res.ok) throw new Error(`API error: ${res.status} ${res.statusText}`);
288 return res.json();
289}
290 
291// --- Deduplication ---
292const seenLinkedIn = new Set<string>();
293const seenEmail = new Set<string>();
294 
295function isDuplicate(person: any): boolean {
296 const li = person.linkedin_url;
297 const em = person.email;
298 if (li && seenLinkedIn.has(li)) return true;
299 if (em && seenEmail.has(em)) return true;
300 if (li) seenLinkedIn.add(li);
301 if (em) seenEmail.add(em);
302 return false;
303}
304 
305// --- CSV helpers ---
306function escapeCSV(val: any): string {
307 if (val == null) return '';
308 const s = String(val);
309 return s.includes(',') || s.includes('"') || s.includes('\n')
310 ? `"${s.replace(/"/g, '""')}"` : s;
311}
312 
313function resultToRow(r: any): string[] {
314 const p = r.person || {};
315 const c = r.company || {};
316 return [
317 p.first_name, p.last_name, p.full_name, p.current_job_title,
318 p.email, p.email_status, p.phone, p.linkedin_url,
319 p.location?.city, p.location?.state, p.location?.country,
320 c.name, c.domain, c.linkedin_url, c.industry,
321 c.headcount, c.headcount_range,
322 (c.technologies || []).join('; '),
323 c.location?.city, c.location?.state, c.location?.country,
324 ];
325}
326 
327const CSV_HEADERS = [
328 'first_name', 'last_name', 'full_name', 'job_title',
329 'email', 'email_status', 'phone', 'linkedin_url',
330 'person_city', 'person_state', 'person_country',
331 'company_name', 'company_domain', 'company_linkedin', 'company_industry',
332 'company_headcount', 'company_headcount_range',
333 'company_technologies',
334 'company_city', 'company_state', 'company_country',
335];
336 
337// --- Main export ---
338async function exportSearch(filters: ProspeoFilters, outputFile: string, maxResults?: number) {
339 console.log('Running initial search to check result count...');
340 const first = await searchPage(filters, 1);
341 
342 if (first.error) {
343 console.error('API error:', first.message);
344 process.exit(1);
345 }
346 
347 const totalCount = first.pagination?.total_count || 0;
348 const totalPages = first.pagination?.total_page || 0;
349 console.log(`Found ${totalCount.toLocaleString()} total results (${totalPages} pages)`);
350 
351 // Check if we need state-by-state splitting
352 const needsSplit = totalCount > 20000
353 && filters.person_location_search?.include?.some(l => l === 'United States #US');
354 
355 if (needsSplit) {
356 console.log('Search exceeds 20K — switching to state-by-state mode...');
357 await exportByState(filters, outputFile, maxResults);
358 return;
359 }
360 
361 // Simple pagination
362 const rows: string[][] = [];
363 const pagesToFetch = maxResults ? Math.min(Math.ceil(maxResults / 25), totalPages) : totalPages;
364 const creditsEstimate = pagesToFetch;
365 console.log(`Will fetch ${pagesToFetch} pages (~${creditsEstimate} credits)`);
366 
367 // Process page 1 results we already have
368 for (const r of first.results || []) {
369 if (!isDuplicate(r.person)) rows.push(resultToRow(r));
370 }
371 console.log(` Page 1/${pagesToFetch} — ${rows.length} contacts`);
372 
373 for (let page = 2; page <= pagesToFetch; page++) {
374 if (maxResults && rows.length >= maxResults) break;
375 const data = await searchPage(filters, page);
376 for (const r of data.results || []) {
377 if (!isDuplicate(r.person)) rows.push(resultToRow(r));
378 }
379 if (page % 50 === 0 || page === pagesToFetch) {
380 console.log(` Page ${page}/${pagesToFetch} — ${rows.length} contacts so far`);
381 }
382 }
383 
384 writeCSV(outputFile, rows);
385}
386 
387// --- State-by-state export ---
388const US_STATES = [
389 'California', 'Texas', 'Florida', 'New York', 'Illinois', 'Pennsylvania',
390 'Ohio', 'Georgia', 'North Carolina', 'Michigan', 'New Jersey', 'Virginia',
391 'Washington', 'Arizona', 'Massachusetts', 'Tennessee', 'Indiana', 'Missouri',
392 'Maryland', 'Wisconsin', 'Colorado', 'Minnesota', 'South Carolina', 'Alabama',
393 'Louisiana', 'Kentucky', 'Oregon', 'Oklahoma', 'Connecticut', 'Utah', 'Iowa',
394 'Nevada', 'Arkansas', 'Mississippi', 'Kansas', 'New Mexico', 'Nebraska',
395 'Idaho', 'West Virginia', 'Hawaii', 'New Hampshire', 'Maine', 'Montana',
396 'Rhode Island', 'Delaware', 'South Dakota', 'North Dakota', 'Alaska',
397 'Vermont', 'Wyoming',
398];
399 
400async function exportByState(filters: ProspeoFilters, outputFile: string, maxResults?: number) {
401 const rows: string[][] = [];
402 
403 for (let i = 0; i < US_STATES.length; i++) {
404 if (maxResults && rows.length >= maxResults) break;
405 
406 const state = US_STATES[i];
407 const stateFilters = JSON.parse(JSON.stringify(filters));
408 stateFilters.person_location_search.include =
409 stateFilters.person_location_search.include.map((loc: string) =>
410 loc === 'United States #US' ? `${state}, United States #US` : loc
411 );
412 
413 const first = await searchPage(stateFilters, 1);
414 const stateTotal = first.pagination?.total_count || 0;
415 const statePages = first.pagination?.total_page || 0;
416 
417 if (stateTotal === 0) {
418 console.log(` [${i + 1}/50] ${state}: 0 results, skipping`);
419 continue;
420 }
421 
422 // Process page 1
423 for (const r of first.results || []) {
424 if (!isDuplicate(r.person)) rows.push(resultToRow(r));
425 }
426 
427 // Paginate remaining
428 for (let page = 2; page <= statePages; page++) {
429 if (maxResults && rows.length >= maxResults) break;
430 const data = await searchPage(stateFilters, page);
431 for (const r of data.results || []) {
432 if (!isDuplicate(r.person)) rows.push(resultToRow(r));
433 }
434 }
435 
436 console.log(` [${i + 1}/50] ${state}: ${stateTotal.toLocaleString()} results — ${rows.length.toLocaleString()} total contacts`);
437 }
438 
439 writeCSV(outputFile, rows);
440}
441 
442// --- Write CSV ---
443function writeCSV(outputFile: string, rows: string[][]) {
444 const lines = [CSV_HEADERS.join(',')];
445 for (const row of rows) {
446 lines.push(row.map(escapeCSV).join(','));
447 }
448 writeFileSync(outputFile, lines.join('\n'), 'utf-8');
449 console.log(`\nExport complete!`);
450 console.log(` File: ${outputFile}`);
451 console.log(` Contacts: ${rows.length.toLocaleString()}`);
452 console.log(` Credits used: ~${seenLinkedIn.size + seenEmail.size > 0 ? 'see above' : rows.length / 25}`);
453}
454 
455// --- Entry point ---
456// Claude will fill in the filters based on your search description
457const filters: ProspeoFilters = {
458 // FILTERS_GO_HERE
459};
460 
461const outputFile = 'prospeo-export.csv';
462exportSearch(filters, outputFile);
463```
464 
465---
466 
467## Credit Cost Estimation
468 
469Before running, Claude will estimate the credit cost:
470 
471| Total Results | Pages | Credits | Approximate Cost (varies by plan) |
472|--------------|-------|---------|-----------------------------------|
473| 1,000 | 40 | 40 | ~$2 |
474| 5,000 | 200 | 200 | ~$10 |
475| 25,000 | 1,000 | 1,000 | ~$50 |
476| 100,000 (state split) | ~4,000 | ~4,000 | ~$200 |
477 
478Claude will always tell you the estimated cost and ask for confirmation before running the full export.
479 
480---
481 
482## Example Conversations
483 
484**Simple search:**
485> "Export all CEOs at 11-50 person companies in California in the SaaS industry with verified emails."
486 
487**Large US-wide search:**
488> "I need every VP of Sales and Head of Sales in the US at companies with 50-500 employees. The Prospeo UI shows 87,000 results."
489 
490**With exclusions:**
491> "Marketing Directors in the US, exclude staffing and recruiting industries, 20-200 headcount, must have verified email."
492 
493**With technology filter:**
494> "CTOs at companies using Shopify in the US, any company size."
495 
496---
497 
498## Troubleshooting
499 
500### "Set PROSPEO_API_KEY environment variable first"
501Your API key isn't set. Run: `export PROSPEO_API_KEY="your_key"` in your terminal.
502 
503### "API error: 401"
504Your API key is invalid. Check it at [prospeo.io/app/settings/api](https://prospeo.io/app/settings/api).
505 
506### "API error: 402"
507You're out of credits. Top up your Prospeo account.
508 
509### "API error: 429"
510Rate limited. The script handles this automatically with exponential backoff. If it persists, you're making too many concurrent requests — only run one export at a time.
511 
512### Results seem low
513- Check that your location format is correct (must include `#US`, `#GB`, etc.)
514- Broaden your title filters — Prospeo does fuzzy matching by default
515- Remove the `person_contact_details` filter to see all results (not just those with verified emails)
516 
517### Duplicates across states
518The script deduplicates by LinkedIn URL automatically. Some contacts may appear in multiple state searches if they've relocated — the dedup handles this.
519 
520---
521 
522## Requirements
523 
524- **Node.js 18+** (for native `fetch` support)
525- **TypeScript** (`npm install -g tsx` to run .ts files directly)
526- A Prospeo account with API credits
527 
528No other dependencies needed — the script uses only built-in Node.js modules.
529 
530---
531 
532## What to do next
533 
534**Run `/icp-prompt-builder`** on a 50-contact sample (required step above). Apply the tuned prompt to your full export, then `/list-quality-scorecard` to grade.
535 
536Next: `/campaign-copywriting` → `/smartlead-campaign-upload-public`.
537 
538**Or wait:** if Prospeo returned <500 contacts for your filter, your ICP may be too narrow. Broaden titles (add synonyms) or industries before scaling.
539 
540## Related skills
541 
542- `/icp-prompt-builder` — required qualification pass before scaling
543- `/list-quality-scorecard` — grade the filtered list
544- `/campaign-copywriting` — write the emails
545- `/smartlead-campaign-upload-public` — launch in DRAFT
546 

Discussion

Alternatives

API and interface designGuides stable API and interface design. Use when designing APIs, module boundaries, or any public interface. Use when creating REST or GraphQL endpoints, defining type contracts between modules, or establishing boundaries between frontend and backend.Coding · MITContext7Pulls up-to-date, version-specific library docs and code examples into the prompt so the AI stops inventing old APIs.Coding · MITContext7 Documentation LookupFetch up-to-date documentation and code examples for any library, framework, SDK, CLI tool, or cloud service. Use whenever the user asks about a specific library — even well-known ones like React, Next.js, Prisma, Express, Tailwind, Django, or Spring Boot — because training data may not reflect recent API changes or version updates. Always use for: API syntax questions, configuration options, version migration issues, "how do I" questions mentioning a library name, debugging that involves library-specific behavior, setup instructions, and CLI tool usage. Use even when you think you know the answer. Do not rely on training data for API details, signatures, or configuration options — they are frequently out of date. Prefer this over web search for library documentation.Coding · MITAdaptyv Bio Foundry APIHow to use the Adaptyv Bio Foundry API and Python SDK for protein experiment design, submission, and results retrieval. Use this skill whenever the user mentions Adaptyv, Foundry API, protein binding assays, protein screening experiments, BLI/SPR assays, thermostability assays, or wants to submit protein sequences for experimental characterization. Also trigger when code imports `adaptyv`, `adaptyv_sdk`, or `FoundryClient`, or references `foundry-api-public.adaptyvbio.com`.Science · MIT