ICP scoring: the exact formula we use to score a prospect against your ideal customer profile
Every prospecting tool shows you an ICP fit score. Almost none show you the math. Here is our account scoring model, weights and all, plus the reason the language model is not allowed to produce the number.
Fit score: 87. Out of what? Based on what? If it were 79, what changed?
Nearly every prospecting product hands you an ICP scoring number like that and expects you to sort by it. When I was researching the space, the scores-without-evidence pattern showed up over and over: seven weighted dimensions here, an "ideal customer profile fit" percentage there, never a breakdown you could argue with.
So here is ours, completely. Not because it's clever (it's coarse on purpose) but because a score you can read is a score you can tune, and a score you can't read is a vibe with a decimal point.
ICP scoring, the way we do it: run a fixed set of checks, each returning pass, fail or unknown with evidence; compute four sub-scores (ICP fit, problem fit, timing, evidence) deterministically from those verdicts; combine them 35/35/15/15; then gate the result on minimum evidence before showing it.
First principle: the model judges checks, code computes scores
This is the decision everything else follows from.
A language model is good at reading an About page and answering "does this company operate in freight brokerage?" It is bad at being asked "how good a fit is this company, 0 to 100?" You get a confident number, you get a different confident number if you ask again, and you can never find out which page it was looking at when it decided.
So the model never sees the word "score." It answers individual, narrow questions and must attach a quote and a URL to any pass. The arithmetic happens after, in plain code, the same way every time. Two people running the same company get the same number. That's the bar.
The checks behind the ICP score
Every company that survives screening gets the same set.
| Group | Check | Decided from | Fail disqualifies? |
|---|---|---|---|
| Basic fit | industry | homepage / about vs ICP industries | yes |
| geography | footer address, careers locations, legal page | yes | |
| size | a public headcount claim, or careers volume; never inferred from "looks big" | only if verified out of range | |
| company_type | SaaS / agency / services / marketplace vs ICP | yes | |
| business_model | self-serve vs sales-led, B2B vs B2C | yes if wrong side | |
| Problem fit | problem_evidence | public evidence they have the problem you solve | yes if fail; keep if unknown |
| use_case_overlap | their product or ops overlaps your use case | no | |
| Timing | hiring | relevant open roles inside 180 days | no |
| funding_or_growth | dated press, "we raised", expansion | no | |
| launch_or_expansion | new product or market announcement | no | |
| tech_adoption | stated tools relevant to your pitch | no | |
| Disqualifiers | competitor | sells what you sell | yes |
| not_a_company | directory, blog, dead site, listicle | yes | |
| already_customer | matches your named customers or case studies | yes | |
| activity | "acquired by", shutting down, copyright year two years stale, last post 18+ months old, parked domain | yes if fail | |
| excluded_by_user | matches the ICP exclude list | yes |
Notice how many are boring and deterministic. already_customer is a domain match against your own case studies. activity is mostly regex. geography is a footer address. The model is reserved for the two judgments that genuinely need reading comprehension: does this company have the problem, and does their world overlap ours.
The four sub-scores in the account scoring model
Each is an integer from 0 to 100.
ICP fit. The basic-fit checks, mapped pass = 100, unknown = 50, fail = 0, then a weighted average: industry 35, company type 25, geography 25, size 15. Size is lowest because it's the one most often unknown, and I didn't want a missing headcount to tank an otherwise perfect match.
Problem fit. problem_evidence is 55 percent of it, use_case_overlap 25, tech_adoption 20. That last inclusion took me a while to accept. Tech adoption looks like a timing signal, but "this company runs Zendesk" is evidence a support function exists, which is need evidence. So it counts toward problem fit, not just timing.
Timing. The share of timing checks passing, with unknown worth 25. Four checks, all unknown, gets you 25. Two passes and two unknowns gets you 62.
Evidence. The share of material claims (headcount, customers, tech, jobs, funding, people) that are verified with a source URL. This is the one that punishes a beautifully argued card with nothing to back it up.
Then:
total = 0.35 × ICP + 0.35 × Problem + 0.15 × Timing + 0.15 × Evidence
rounded to the nearest 5 in the UI, because 87 versus 84 is false precision and people will act on it anyway.
Labels: 85 and up is Excellent fit. 70 to 84 is Strong. 50 to 69 is Possible. Under 50 doesn't show by default.
Any hard disqualifier skips all of this. The company is dropped with the reason written in the run inspector, so you can see why something you expected to appear didn't.
The gates that took the longest to get right
The formula above was done in an afternoon. The guards around it took a week of looking at bad cards.
Verified means the quote is on the page. Early on, "verified" meant the model said it found something on a page we'd fetched. Then I found a card citing an employee count from an About page that didn't contain any number at all. The model had inferred it and attached the URL it liked. Now verified requires the source URL to be a page we actually read and the quote to appear in that page as a whitespace-normalised substring. If the string isn't there, it isn't verified, no matter how plausible.
Problem evidence needs a quote. problem_evidence = pass is forced back to unknown unless at least one verified evidence item with a quote supports it. This is the single check with the most weight in the whole model, and it's the easiest one for a model to hand-wave. So it's not allowed to.
Minimum evidence to be shown at all. A prospect appears in results only with at least two verified claims and a known industry or company type. Everything else goes into an "insufficient evidence" bucket. You can open it. You're not meant to act on it.
Recency. Evidence carries an observed date when there is one, and timing checks only count inside a 180-day window. A job posted last year doesn't make "reach out now" true.
Confidence is a separate number from ICP fit, and it's about evidence
People conflate these. A company can be a perfect fit with low confidence (thin website, one verified claim) or a middling fit with high confidence (we found everything and half of it was mediocre).
Confidence is: evidence score at least 75 and at least three verified claims gives high; at least 50 gives medium; anything else low. It's shown next to the fit label, not blended into it, because you'd act on those two situations differently.
What this looks like on a card
Northwind Freight Systems · northwindfreight.example
Fit: Excellent ICP 95 · Problem 90 · Timing 60 · Evidence 90
Why: mid-size 3PL, dispatch on manual processes, hiring 3 ops
coordinators, stated 2026 goal to automate shipment comms
✓ "over 240 employees across 6 terminals" /about
✓ 3 open Operations Coordinator roles /careers
✓ "automate shipment status communication in 2026" /news
? Funding Not found
Reason to reach out now: 3 ops roles posted this quarter
Likely buyer: VP Operations
Confidence: High
Every number above can be traced to a row in the checks table and a line on a page. If you disagree with the 60 on timing, you can see it's two unknowns and you can go look. That's the entire point.
Steal the scoring model, adjust the weights
The weights are opinions. Yours may differ, and they should if you're selling something where timing matters more than fit (event-driven products) or where geography is everything (regulated markets). What I'd keep no matter what:
- The model answers narrow questions; code does the arithmetic
- Pass requires a quote that's actually on the page
- Unknown is a first-class answer, not a fail and not a guess
- A minimum-evidence gate before anything is shown
- Confidence separate from fit
If you want to see the checks feeding this formula come out of real pages, the discovery side is covered in how to build a target account list from your website, and the hiring check specifically in how to read a careers page. Or run it on your own site with find-customer, where the score breakdown is printed on every card exactly as above.
