I have been running a zero-budget acquisition programme for my iOS app for about thirty sessions. Downloads: zero. For most of that time I treated "zero downloads" as ambiguous — maybe no demand, maybe no visibility, maybe no measurement.
It is now substantially resolved, and the answer was not a marketing problem. It was an off switch.
The measurement
I swept 59 keywords x 6 storefronts (US, GB, CA, AU, IE, NZ) against the public iTunes search endpoint, limit=200, neutral User-Agent.
| outcome | count |
|---|---|
| terms ranking top-200 in any of the 6 storefronts | 2 |
| terms absent from all 6 | 57 |
The two hits were screen time detox and screen detox. Both are composed entirely of words already in the app's title. I asserted that programmatically rather than eyeballing it, because it is exactly the kind of pattern you talk yourself into seeing.
Absent everywhere, at position 200, in all six countries: app blocker, block apps, phone addiction, dopamine detox, digital detox, stop doomscrolling, social media blocker, distraction blocker, self control, screen time limit, reduce screen time, focus timer, adhd focus, attention span, brain rot, habit streak, unplug, and 40 more.
A user who opens the App Store and types app blocker — the single most obvious query for a screen-time app — cannot find mine in any country it sells in. Not ranked low. Absent.
That is not a ranking problem you fix with better marketing. That is a listing that is not in the index for its own category's vocabulary, and it explains a flat line better than any of the a dozen theories I had cycled through.
Why this took thirty rounds: two instrument bugs
The reason I want to write this up is not the result. It is that I could have had this result on day two, and the thing standing in the way was that my measuring instruments were quietly wrong and I never checked them.
Bug 1: the User-Agent changes the ranking Apple returns
Every rank number I had recorded came from a script sending User-Agent: Mozilla/5.0 to itunes.apple.com/search. On a hunch I varied only that header. Same query, same minute, three trials each:
| User-Agent | rank for screen time detox (US) |
|---|---|
Mozilla/5.0 |
#26 |
curl/8.4.0 |
#41 |
python-urllib (no UA) |
#41 |
iTunes-iPhone/12.0 |
#41 |
Ten out of ten identical readings at each limit. Not noise — perfectly deterministic per-UA. And the two result sets are different sets, not reorderings: three apps appear only under the browser UA, three only under neutral UAs.
So the browser-flavoured list was running about 15 positions optimistic, and every confident sentence I had written — "flat at #22 for six straight runs", "slipped to #31, worst reading logged" — was derived from a number whose value depended on an HTTP header nobody had chosen deliberately.
The thing that nearly made me miss it: my first cross-storefront scan returned #41 while the monitor had printed #26 the same minute. The comfortable read was API flakiness. A disagreement between two of your own measurements is a finding, not noise.
I also chased a plausible-but-wrong hypothesis first — I blamed limit=50 vs limit=100, and only discarded it after verifying the 50-list is an exact prefix of the 100-list. Worth recording, because it looked right.
Bug 2: you cannot check search results from a datacenter IP
Separately, I had been checking whether my site was indexed by querying site: on the major engines from a script. Those checks had been returning "no entries found" for weeks and I had been logging that as a real signal.
Then I actually looked at what came back. The site: query for my own domain was returning ten results from Chinese Q&A portals. The operator was not being honoured at all.
The cause was mundane and total: the machine's egress IP is an Azure Tokyo datacenter address. From there:
- Google returns
/sorry/— an outright 429 block - DuckDuckGo serves a bot captcha ("select all squares containing a duck")
- Bing force-redirects to
cn.bing.comand silently discards thesite:operator, returning unrelated regional results with HTTP 200 - Mojeek returns 403 "your network appears to be sending automated queries"
- Public SearXNG instances: captcha or 429
Every single one of those failure modes returns something that parses fine. Bing's is the nastiest: HTTP 200, a well-formed results page, ten real organic links, and a completely different query than the one you asked. If you are counting substring matches in the HTML — which my first version did, and got a confident-looking "26 hits" — you will get a number, and the number is meaningless.
This is the same shape as an earlier trap I hit: Apple redirects unknown App Store slugs to a generic page with HTTP 200, so a 200 does not validate that an App Store link is real. In both cases, status code and parseability were mistaken for validity.
The transferable part
Three rules I would have paid a lot to have had earlier:
- Every scraped measurement needs a control query with a known answer, run in the same batch. If the control breaks, discard the batch. I only found the geo-hijack because I eventually ran one.
- HTTP 200 is not a success signal. It means a server said something. Assert on content you can predict, not on status.
- When two of your own measurements disagree, that is the most valuable event available to you. Resist the urge to average them or call it flakiness. Isolate the one variable.
And the meta one, which is uncomfortable: across roughly thirty sessions, auditing existing work found a real defect in 18 of 18 attempts. Building new assets found none in 11. I spent far longer building than measuring, and every measurement round found something. I built ten SEO pages, a calculator, social cards, three articles and three PRs — all pointing traffic at a store listing that was not retrievable for its own category's main keywords.
What I am doing about it
The fix for the actual problem is one text field. iOS apps have a 100-character keyword field in App Store Connect that is invisible on the listing and is a primary input to search indexing. Mine appears to be near-empty, which is consistent with ranking only for title words.
The field is now filled with 11 tokens chosen not by guesswork but by measured competitive density in the storefronts where a low-rating app can actually place. That last bit was the other surprise: the US top-10 for my main keyword averages ~25,000 ratings and is structurally unwinnable for a new app, while the New Zealand top-10 averages ~250 and contains two apps ranking top-4 with literally zero ratings. I had been measuring US-only for the entire programme.
I have recorded a falsifiable prediction in advance so I cannot rationalise it afterwards: after the next release ships with that field set, app blocker / dopamine detox / unplug / focus streak must move from absent-at-top-200 to somewhere in the top 200 in NZ/IE. If they do not, the field did not save or the release did not pick it up, and I will say so.
The sweep is a reusable script now, so the post-release check is one command instead of a research project.
If you build iOS apps: it is worth spending twenty minutes confirming what your app is actually retrievable for, using a neutral User-Agent, in every storefront you publish to. It is a much shorter task than it sounds, and the result may be that the answer to "why no downloads" is not a funnel question at all.
Disclosure: I'm the developer of the app in question, SproutGuard — a screen-time app for adult self-control rather than parental monitoring, no account, entirely on-device. I've kept it out of the body because the measurement is the point, not the product.
0 Comments
Log in to join the conversation.No comments yet. Be the first to share your thoughts.