An ICP list nobody can fetch isn't targeting. It's a wish list.
I asked an AI for 25 ideal customer profiles. It gave me 25. Then the scraper went out and found almost nobody, and nothing in the system said anything was wrong.
That silence is the problem. Here is exactly what broke, and what I built to stop it.
The part everyone gets right
Generating ICPs is the easy half now. Point any decent model at your website and it will hand back a tidy list: industry, company size, job titles, a paragraph of pain. It reads well. You can put it in a deck.
Then you take that list to the machine that actually finds the leads. That is where it falls apart, and it falls apart quietly.
Failure one: the word that returns zero
Google Places has a closed vocabulary of business types. hotel is real. bank is real. hospital and marketing_agency are real. chemical_company is not. Neither is b2b_saas.
An AI writing an ICP does not know that. It writes chemical_company because it sounds right. SpiderMaps then filters on that exact string, matches nothing, and returns an empty result set. No error. No warning. Just zero.
Now look at what you see on your side. Zero results for "chemical manufacturers in Germany." You conclude the market is thin and you kill the ICP. You were wrong, and you paid for a scrape slot to be wrong.
This is the single most expensive failure in the whole funnel, because it does not look like a failure. It looks like an answer.
Failure two: 25 profiles, one company
Ask for 25 ICPs and you usually get 25 adjectives on the same company. Mid-market B2B SaaS. Growing B2B SaaS. Scaling B2B SaaS with a support problem. That is one segment wearing 25 hats.
Feed those to a lead engine and it runs 25 nearly identical searches. Dedup then strips the overlap. You paid for 25 and you got 1.

A segment is not an ICP. Boutique hotel, resort, and bed and breakfast are three real profiles inside one sector. They buy differently, they are staffed differently, and they are found with different queries. Twenty five variations on "mid-market" are not.
The reframe: plan the fetch, not just the list
Both failures come from the same root. The list was written as if the list were the deliverable.
It is not. An ICP with no method to fetch the leads is a wish list. So I built @di-atomic/icp-discovery as a planner rather than a generator, and I made the acquisition step a first-class method instead of an afterthought.
For every profile it produces, planAcquisition emits the flow, a ready to run payload, an estimated credit cost, the expected output, and the reasoning behind the choice. Two real examples from the shipped reference:
A boutique hotel. Valid Places type hotel, fit score 8, and you need email addresses. Route it to the full lead chain with {search_query: "boutique hotel in Lisbon", max_results: 50}. The location goes inside the query string, never in a separate field. That one detail silently breaks more jobs than anything else in the stack.
A non-EU chemical manufacturer that needs an Only Representative. No storefront, so the Places type is correctly empty. It carries Regulatory Affairs Manager in its titles instead, so it routes to a LinkedIn search with {mode: "search", search_query: "Regulatory Affairs Manager chemicals EU", country_code: "DE"}. Under REACH, that role exists because the regulation created it, which is why this kind of profile tends to rank high on fit.

Same skill, two completely different acquisition paths, decided by which fields are actually usable rather than by a guess.
The proof: two scripts that fail the build
I do not want to trust a model's judgment on this, so the skill ships two verifiers you run before anything reaches a paid scrape.
verify-icp-shape.mjs checks the fields that break things downstream. Every googlemapsType must be in the real Places allow list or empty, and empty is the correct answer for anything sold online. Fit scores must be integers from 1 to 10. Industry codes must be numeric. Pain points cannot be blank.
verify-icp-diversity.mjs checks that you got 25 profiles instead of 1 profile 25 times. No two ICPs may share the same industry, size band, and category. The set must vary across at least three of industry, size, operating model, geography, and seniority. Pairwise text overlap stays under 0.6. Any set of ten or more needs at least two size bands.

Both scripts pass the shipped exemplars and fail a deliberately bad set. They caught chemical_company, they caught a fit score of 15, and they caught a collapsed all-SaaS set. That is the point. A gate that never fails is decoration.
One honest note on that fit score. It is an editorial 1 to 10 sort key that tells you what to run first. It is not a measured close rate, and I will not pretend otherwise. Scoring your targets is designed to improve how you spend your search budget. It does not promise you a percentage.
What to do with this
If you are already generating ICPs, run one check tonight. Take your list and look at every Places type on it. If you see anything that is not in Google's actual vocabulary, you have been reading empty results as market signal, possibly for months.
Then ask the harder question of each profile: what exact query finds these people, and what does it cost? If you cannot answer that, you do not have a target. You have a description.
The skill ships with 10 sourced exemplar profiles across 9 verticals, including chemicals and REACH compliance, hospitality, healthcare, fintech, manufacturing, and enterprise security. Seed the one nearest your product and the model stops inventing from scratch.
@di-atomic/icp-discovery is live on the marketplace. It plans the targeting and hands the plan to SpiderIQ to run. It does not scrape, and it does not write the emails. It makes sure that when the scraper does go out, it goes out with a query that can actually come back.
You can always optimize a number. You cannot optimize a zero that was never real.