find-customer

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.

Aman Jha··7 min read

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

ICP scoring weights: ICP fit 35 percent, problem fit 35 percent, timing 15 percent, evidence 15 percent, with the sub-weights for each and the label thresholds

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.

Keep reading