
Cover photograph by Aedrian Salazar on Pexels.
← Insights & ArticlesA number you can compute in thirty seconds is not the same as a number that means something.
Aug 26, 2026 · 10 min read
I built a program that reads a company's website and reports what the business actually sells. Then I had to decide what to call it, which is a question with money attached, because on a marketplace the name is most of the discovery.
Two candidates. Company website analyzer describes exactly what the thing does. Company enrichment is jargon, it describes the category rather than the program, and it is what people in this market seem to type. Describing your product and being found are not the same objective, and where they disagree you have to pick one.
There is a public endpoint that can settle it. The marketplace exposes its store search over HTTP, no key, no account, and every listing comes back with lifetime run and user counts attached. So you can ask, for any phrase, how much activity sits behind it.
The obvious thing to do is add them up.
GET https://api.apify.com/v2/store?search=company%20enrichment
GET https://api.apify.com/v2/store?search=website%20enrichment
sum(totalRuns) company enrichment 37,091
sum(totalRuns) website enrichment 2,399Fifteen to one. That is not a close call, it is not within any reasonable margin, and it points hard at the jargon. I had the number, it came from a source anybody could check, and it agreed with what I already suspected, which should have been the first warning.
Then I went to check it before publishing it, and it fell apart.
The endpoint takes a limit parameter. I had left it alone. Here is the same pair of sums at five values of it, run within a few minutes of each other:
| limit | company enrichment | website enrichment | Which wins |
|---|---|---|---|
| 20 | 39,020 | 4,014 | company, by 10x |
| 50 | 39,896 | 849,175 | website, by 21x |
| 100 | 98,316 | 865,267 | website, by 9x |
| 200 | 6,141,598 | 872,843 | company, by 7x |
| default | 32,619,545 | 5,784,959 | company, by 6x |
The answer flips between the first and second rows and flips back between the third and fourth. The magnitudes move across three orders of magnitude. Every one of those numbers is a real sum of real counters returned by the real endpoint, and the parameter I changed is documented as controlling how many results come back.
My original figure is the top row, near enough. It was correct. It was also an artifact of a page size I never consciously selected.
Ask the endpoint for a given number of results and count what arrives:
limit=500 -> 405 items (total reported: 5,312)
limit=1000 -> 813 items (total reported: 5,326)
limit=2000 -> 813 items (total reported: 5,326)You never get what you asked for, the shortfall is not constant, and past a certain point asking for more returns nothing more. The reported total does not match either, and it is not even stable between two calls a second apart.
Whatever limit is doing, it is not selecting the first N of a fixed ordered list. It behaves like a size for some candidate pool that then gets filtered, which means raising it does not append more results to the ones you already had. It changes which listings are in the set. That is why the sums do not grow smoothly, and it is why the winner changes.
This is the part worth taking away, because it does not depend on this endpoint or this marketplace.
The search is fuzzy. It returns things that are sort of related to the phrase, ranked by relevance, and it keeps going well past the listings that are actually named for it. So the tail of the result set fills up with large general-purpose tools that match loosely. When you sum a counter across that set, the total is dominated by whichever giant happens to have been admitted.
Here is what was actually inside the numbers, at limit 200:
| Phrase | Top contributors | Share of the sum |
|---|---|---|
| company enrichment | Two Google Maps extractors | 92.6% |
| website enrichment | One Google Maps scraper | 54.1% |
Neither phrase appears in what those programs do. They are map scrapers. My demand metric for two enrichment phrases was, to within a rounding error, a measurement of where three unrelated Google Maps tools happened to sit in a relevance ranking.
Any statistic whose value depends on how many results you asked for is measuring your request, not the world.
A sum over a relevance-ranked set has this property built in. There is no natural boundary: relevance decays continuously and nothing tells you where the phrase stops applying. An average has the same problem. A count has it too, unless you decide what you are counting on grounds other than the ranking.
The fix is to stop letting the search engine decide what is in the set. Keep only the listings whose own name or title contains both words of the phrase. That is a rule I can state, a reader can apply, and the endpoint has no say in.
Same five page sizes, with that filter applied:
| limit | company: listings | company: runs | website: listings | website: runs |
|---|---|---|---|---|
| 100 | 39 | 68,147 | 8 | 2,586 |
| 200 | 41 | 181,115 | 9 | 2,626 |
| 500 | 46 | 181,570 | 13 | 2,821 |
| 1000 | 52 | 182,320 | 14 | 2,881 |
The direction never flips. The values climb and settle rather than swinging. Two consecutive calls at the same page size return identical figures. It converges on 52 listings against 14, and roughly 63 to 1 on runs, which is a stronger result than the one I was going to publish and is the first version of it I would defend.
It also survives the check that matters most, which is time. The same measurement taken three days earlier put the listing named exactly company enrichment at 175 users and 25,550 runs. Today it reads 185 users and 25,821 runs: ten more users, 271 more runs. Slow movement in one direction is what a real counter looks like. The sums in the first table have no such property, because there is nothing underneath them that could drift.
The question I started with gets a clear answer, and then an uncomfortable one.
| Phrase | Listings named for it | Runs across them | Users on the listing named exactly that |
|---|---|---|---|
| company enrichment | 52 | 182,320 | 185 |
| website enrichment | 14 | 2,881 | 5 |
| company website analyzer | 1 | 50 | 3 |
The last column needs its rule stated, because the filter alone does not give it. Sort the 52 by users and the top entry is a LinkedIn scraper with 770 users that carries the phrase in its title, which tells you about that program rather than about the phrase. The column above instead reads the listing whose name is the bare phrase and nothing else, which is the closest thing available to a like-for-like comparison. Both figures are in the data and I would rather show you the rule than the tidier number.
The name that describes the product precisely has one listing in the entire marketplace and it has three users. That is not an underserved niche waiting to be taken. It is a phrase nobody types, and a listing named for it is invisible by construction. So the jargon wins, the original conclusion stands, and it now rests on something I can hand to a stranger.
Now the uncomfortable part.
Fifty-two listings compete for the winning phrase. The median one has three users. On the losing phrase the median is four. The distributions are almost identical in the middle: the difference between the two phrases lives entirely in the head, in a handful of listings that got established early and now take nearly everything.
My own listing is named for the winning phrase. It has thirteen runs and one user.
So the measurement told me something true and narrow. It told me which phrase has an audience. It did not tell me I would get any of that audience, and the same data says plainly that most of the listings named for it do not. I could have read the 63 to 1 as a forecast. It is a description of a room, not an invitation into it.
Demand research tells you where the room is. It says nothing about whether you get in.
The filter is a blunt instrument and it is wrong in both directions. It misses listings that serve the same demand under a synonym, and it admits listings that put the phrase in their title while doing something else: the top result for website enrichment by users is a Google Maps email extractor whose title carries the words. I kept it, because a listing that names itself for a phrase is competing for that phrase whatever it does underneath, but it means these counts describe naming behaviour more precisely than they describe demand.
Run and user counts are lifetime totals, not recent activity. A listing that was busy two years ago and is dead today looks identical to one that is busy now. That flatters incumbents and understates anything new, which is exactly the wrong bias for the question I was asking.
The reported result total is unstable between calls and the fuzzy matching is opaque. I cannot tell you what the search is doing internally, only that its output does not behave the way a paged list behaves. Everything above is built to survive that rather than to explain it.
And this is one marketplace, measured on one day, for one pair of phrases in one category. The method transfers. These particular numbers do not.
This is the only research I have published that needs no trust at all. The endpoint is public, it takes no key, and the whole thing is one request plus a filter:
curl -s "https://api.apify.com/v2/store?search=company%20enrichment&limit=1000" \
| python3 -c '
import json, sys
items = json.load(sys.stdin)["data"]["items"]
words = ["company", "enrichment"]
named = [i for i in items
if all(w in (i["name"] + " " + i.get("title", "")).lower()
for w in words)]
named.sort(key=lambda i: -i["stats"]["totalUsers"])
print(len(named), "listings")
for i in named[:5]:
print(i["username"] + "/" + i["name"],
i["stats"]["totalUsers"], "users",
i["stats"]["totalRuns"], "runs")
'Change the phrase, change the word list, run it again. If your numbers differ from mine by a little, that is the counters moving, which is what they should do. If they differ by a lot, I would genuinely like to know, and that is a sentence I can only write because the method is small enough to be wrong in public.
I work to a rule that a number published anywhere has to rest on an artifact that can still be produced. This one passed that test on the first attempt. The endpoint was public, the request was reproducible, the sum was arithmetic, and I could have handed anyone the command.
It passed, and it was still wrong, because reproducibility is not validity. A reproducible measurement of the wrong quantity reproduces perfectly. Worse, publishing this one would have handed every reader the exact tool needed to find out it was nonsense, in about thirty seconds, on a site whose entire argument is that its numbers survive being checked.
So the rule grew a second half:
Before publishing a number, change one thing that should not matter and confirm it does not. If nothing in the method was arbitrary, that costs a minute. If something was, you have just found it.
The parameter I had never set was the arbitrary thing. It took one minute to find and it inverted the conclusion of the piece.
The program this naming decision was for is documented at Company Enrichment, with what it does not do on the same page. It is the one with thirteen runs, so read the section above before drawing any conclusion from the fact that I named it for the winning phrase.