GetLeads skill

Find every company like your customers

Give your AI your customer list. This skill finds every company in your market that looks like them, including the ones a keyword or industry search misses: the recruiter filed under Hospitals and Health Care, the agency that never calls itself an agency. It pulls wide, cleans the list with AI, and tells you how many it found that a normal search would have missed.

Paste this into your AI
Install the GetLeads company-list skill from https://getleads.io/company-list/ and use it to find every company like my customers.

Works in any AI that can run code: Claude Code, Codex, Cursor and similar.

What it does

  1. Reads your customers’ LinkedIn pages and websites to work out every industry and keyword a company like them could be filed under, and checks that plan with you.
  2. Pulls every company in all of them from GetLeads, limited to your geography and headcount.
  3. Cleans the list with AI, reading the LinkedIn text first and the homepage only when that can’t settle a company. You get keep, review and cut lists, and for each search, how many companies it found that no other search did.

The people to contact at the kept companies come next, with the get-everyone skill.

What you need

Install steps (for the AI reading this page)

  1. Download getleads-company-list.zip and unzip it into your skills folder (Claude Code: ~/.claude/skills/), so you end up with getleads-company-list/SKILL.md and getleads-company-list/scripts/.
  2. If you can’t unzip, download the files one by one into that same layout:
  3. If you don’t support skills, download the files into your working folder, keeping scripts/ as a subfolder, then read SKILL.md and follow it.
  4. Check the GetLeads MCP tools are available. If they aren’t, send the user to getleads.io/mcp first.
  5. Then follow the skill from step 1.
Download the skill (.zip)
Read the skill

The philosophy is to export a wide and noisy list (because you don't pay with credits), clean with AI after. A semantic or single-filter search returns the part of the market that happens to be labelled the way you guessed; this returns every company that could plausibly be a target, then throws the wrong ones away. A healthcare recruiter filed under Hospitals and Health Care, or a marketing agency filed under Banking, is still caught.

Open by telling the user that benefit in two lines: this finds the companies a simple keyword or industry search misses, the ones filed under the wrong industry or that never use the obvious word, and the final report counts exactly how many.

Filter names and valid values come from get_available_columns and get_available_values; call them rather than assuming.

Keep the user with you. Before each stage, say in one line what it does and why; after it, show the result as a table. The user should see the benefit as it happens, not only at the end:

1. Seeds

Take the user's customer list in whatever shape it arrives and extract domains. No list: ask for ten companies they'd call perfect customers. Ask which geography they sell into.

Profile the seeds in one call: export_contacts with domains = the seeds, max_per_company: 1, and these columns:

company_id, company_name, company_website, company_linkedin_url, company_domain, company_description, company_summary, company_industries, main_industry, linkedin_employee_count_exact, employee_count_range, company_hq_location

Save it as seeds.csv. Name any seed GetLeads didn't return. Then fetch every seed's homepage as plain text:

python3 scripts/profile_seeds.py seeds.csv --scrape-cmd '<the scraping command, step 4>' > seeds_profile.json

Pages a plain fetch can't read go through the user's scraping tool (step 4). A homepage_status other than live, or homepage_text still thin after that, means read that site with a browser instead.

For each seed, record four things:

  1. LinkedIn industry, as filed: industry_filed in the profile (main_industry), the label the industries filter takes. The industry tags are for reading, not filtering: the filter can reject them.
  2. LinkedIn industry, from the website: read the homepage and pick the value from get_available_values("industries") that the company actually belongs in. Companies file themselves wrong (a healthcare recruiter filed under Hospitals and Health Care), and this is how those get caught. Pick more than one when the site supports it.
  3. Keywords we'd expect in the LinkedIn About text: knowing what the company does from the website, write the words a company like it would use to describe itself on LinkedIn, since that's the text the search runs against. Check them against the seeds' own about and summary. Include synonyms, abbreviations, singular and plural. Aim for 15–30 across the seeds (recruitment agency, recruiter, staffing agency, staffing firm, executive search, headhunter, talent acquisition, RPO, permanent placement, contract staffing…).
  4. Other routes, from the website: with what the site says the company does, go through the full filter catalogue (get_available_columns, searchable fields) and propose every other filter a company like it could be found through: technologies it would run, funding stage, founding year, hiring. Company search runs on industry, the company description, headcount, HQ location, revenue, funding, founding year, technologies and domain. Each one is a route, pulled whole like the industries.

Also note the headcount range: smallest and largest LinkedIn Employee Count Exact, widened (half the smallest, double the largest) unless the user has a hard band.

Check in before pulling. Show the user one table (seed, industry as filed, industry from website, expected LinkedIn keywords, other routes), then the combined lists: industries to pull (filed and assigned), keywords, other routes, headcount, geography. Wait for their yes or edits. Everything they approve becomes a route in step 2.

2. Routes

From the approved check-in: one route per industry (filed and assigned alike), one per keyword on company_description, and one per other route. Every route carries the same two scope filters: headquarters_countries (or headquarters_cities) for geography, and employee_profiles_on_linkedin_min/max for exact headcount. Don't use count_contacts here: it counts contacts, not companies, and keyword counts often time out. The company count per route comes from step 3.

3. Pull

One export_contacts per route, the same columns as the seeds (the cleaner reads only those), max_per_company: 1, which keeps the export to about one row per company. An export holds at most 50,000 rows, and one that returns exactly 50,000 is full, whatever cap_reason says: split that route by partitioning into smaller searches e.g. splitting by headcount (employee_profiles_on_linkedin_min/max bands) until every part comes back under 50,000. Save each as pulls/<route>.csv; the file name becomes the route label in the report. Exports run as background jobs: start every route's export first, then check them, so they run together (twelve keyword exports: about 6 minutes together, 16 one after another).

The list is the union of every route, deduplicated to one row per company: the cleaner merges all the pulls on the company's own company_id (its website, LinkedIn page or name when there is no ID), keeps the first non-empty value of each field, and records every route that found the company. It never merges on company_domain: that is the exported contact's employer domain, and it can belong to another company. Never judge or deliver a single route on its own.

Then show the user the list and the company counts before anything is judged:

python3 scripts/clean_companies.py --pulls pulls/*.csv --seeds seeds.csv --out run --routes-only

It prints rows pulled against distinct companies, and each route's companies and only_here (found by no other route). Let them drop a route before cleaning.

4. Clean

Write contract.json from the seeds:

{
  "target": "One paragraph: what a target company does.",
  "must_have": ["The one or two things that make a company a target"],
  "exclusions": ["Resellers of other companies' products", "Service providers that use the product for clients", "Training providers"]
}

Jev is asked about each must-have and each exclusion separately, so write every item as one self-contained condition ("Job boards that advertise roles but don't place candidates"), never several conditions in one item.

Run the cleaner. It needs pip install typesafe-sdk and TYPESAFE_API_KEY (typesafe.ai) in the environment, .env or ~/.agents/.env.

Homepages are fetched directly first. The ones that can't be read that way (a bot wall, a JavaScript site, about one in ten) go through whatever scraping tool the user has: Firecrawl, Scrapfly, Apify, ScraperAPI, anything with a command line or an API. Pass it as --scrape-cmd, a shell command that prints the page for {url} (or {url_encoded}) as markdown, text or HTML. For example:

The command must exit non-zero when the scrape fails (hence -f and -e above), or an error message is read as the page. Test it on one URL first. Set --scrape-workers to the tool's concurrency limit; a failed scrape is retried after a wait, since it is usually a rate limit. No tool: those companies are judged on their LinkedIn text alone; say so.

python3 scripts/clean_companies.py --pulls pulls/*.csv --seeds seeds.csv \
  --contract contract.json --out run --limit 60

It unions and dedupes the routes (step 3) and judges every company on its LinkedIn text: About, summary, industry, headcount and HQ. Per company, Jev answers whether it is the kind of company the target describes (a different business, related, or a match), whether the text says concretely what it does, and each must-have and exclusion. A company is settled on that text alone only when it is kept, or cut for a likely exclusion or a clearly different business its text describes in enough detail. Every other company gets its homepage fetched as plain text and is judged again, which is what keeps a run to minutes. The seeds, with their homepages, are the examples, and each seed is also judged with itself left out, which is the test. The Jev version is pinned, so a new release doesn't quietly change who is kept.

Verdicts: cut when an exclusion or a different kind of business is likely, or a must-have unlikely; keep when a match is likely, every must-have likely and no exclusion likely; review otherwise. Nothing is cut on thin text or a doubtful must-have before its homepage has been read. The output CSVs (keep.csv, review.csv, cut.csv, companies_judged.csv) have one row per company: verdict, p_match, p_different, must, excluded, evidence, name, domain, website, headcount, industries, description, and which routes found it. summary.json includes the seconds each stage took.

Calibrate on the seeds plus 60. Two checks in summary.json:

Read the keeps, the reviews and a sample of the cuts, fix the contract, re-run (cached judgments and pages are reused). Then drop --limit for the full run. unjudged above zero means some calls failed (failures.jsonl); re-run to retry just those.

No TypeSafe key: run with --no-judge, which writes run/to_judge.jsonl with the same evidence, and judge it yourself in batches against the contract.

5. Deliver

From run/summary.json and the CSVs:

Then offer contacts at the kept companies: getleads-contact-list (https://getleads.io/get-everyone/) takes keep.csv as it is (its company_id column keeps the contacts to the right companies).