I opened the ad review result and read this: Needs attention — policy violation: low value content.
I thought I knew the cause immediately. My apex domain sits behind a CDN, and that CDN challenges bots. If you curl one of my pages you get a "just a moment" screen instead of content. If that's what the reviewer saw, declaring the site empty is the only sensible verdict. Better still, the hypothesis had a precedent — months ago, on the same review, blocked crawling really had been the cause.
A problem you fix with one toggle. I wanted to believe that.
One question before we start: is there a diagnosis you currently hold because it would be easy to fix? I had one.
My first measurement lied to me
To test the hypothesis I measured body-text length per app: fetch the HTML, strip tags and scripts, count what's left. One result came back like this:
ootssu.com/guides/mbti-compatibility.html 58 charsFifty-eight characters. I looked at that and invented a brand-new bug: the guide page must be failing to render.
What was broken was not the page. It was my measurement. Those 58 characters were the text of the CDN challenge screen. I SSH'd into the server and the source file was 14,989 bytes with the body, structured data and three FAQ entries all intact.
There's an irony here. Trying to verify the claim "the crawler saw a challenge page," I was the one who measured a challenge page. I had reproduced exactly the failure my hypothesis predicted — which does not make the hypothesis true, because different bots get treated differently.
Where the hypothesis died
So I looked at the other side. Are the crawlers actually blocked?
First, my 33 apps don't live on the apex. They're on a separate host, and the response headers show they never touch the CDN at all:
ddi.ootssu.com server: Vercel
tarot.ootssu.com server: Vercel
dev.ootssu.com server: VercelSo nothing stood between the reviewer and the apps. For the blocking story to hold, the judgement would have to rest on a single apex page — but an ad network looks at the whole domain.
Second, I opened the server access log. It only retains 13 hours, but within that window:
Googlebot: 12 (11× 304, 1× 200, 1× 301)
AdsBot-Google: 1
Mediapartners-Google: 0The search crawler was walking in and getting normal responses. If it were blocked, those lines wouldn't exist.
Hypothesis rejected. The comfortable option was wrong.
So what did they actually see?
One possibility was left: the reviewer read the content and then judged it thin. So I re-measured, this time only the apps that bypass the CDN, where the measurement is valid.
mindage 472 chars
ddi 530 chars ← my top-traffic app
bloodtype 814 chars
tarot home 900 chars
lifeweeks 1,296 chars
zodiac 1,689 chars
tarot card 1,706 chars ← the maximumOne input form, one screen of computed result, roughly 500 characters of unique text. Thirty-four of those, all stamped from the same template.
After seeing those numbers the verdict read differently. "Low value content" wasn't a vague complaint. It was an accurate description.
How I confirmed there was no cheap fix
Let me stop and ask you. What would you do? Stuff text into thirty-four apps and go for re-review, or accept the direction the verdict is pointing?
I ran the payoff first. Reading my traffic analytics for the same period, the apps produced 189 sessions in 28 days. Even with ads live at a normal rate, that's about a dollar a month. It doesn't come close to justifying writing real content into thirty-four apps.
The structure was against me too. An ad network's unit of review is the root domain — you can't register subdomains separately. So the thinnest one of the thirty-four gates the entire domain. It's not that I have no thick assets: the apex locale hubs run past 10,000 characters, and the tarot dictionary has 468 pages at 1,706 characters each. They just don't offset thirty shallow calculators.
A third number pointing at the same cause
One last cross-check. Over those same 28 days my ten YouTube channels accumulated 29,625 views. Those views produced 29 app sessions. A 0.098% conversion — one visitor per thousand views.
The reviewer's verdict, my search click count, and that conversion rate. Three measurements from three unrelated places, all saying the same thing: there is no reason on the page for a person to arrive and stay.
The rejection wasn't a loss. It was information — an honest outside assessment, delivered free.
Three things to check on your own project
- Is your measurement tool actually measuring your content? Behind a CDN, a bot wall or a login gate, you may be counting the characters of an error page. Diff it against the source file at least once.
- How many unique characters does your highest-traffic page have? If it's around 500, search engines and ad reviewers are both prepared to reach the same conclusion.
- Do you hold a diagnosis that ends with one toggle? If it's true, lucky you. If it isn't, that hypothesis is blocking you from looking anywhere else.
The honest part
There's no twist in this post. It would read better if I had fixed a crawler setting and won the approval, but what actually happened is that I built a comfortable hypothesis and the data pushed it over. And the data agreed exactly with what every other metric on this domain had been saying for weeks.
The rejection notice is still sitting there. I haven't filed for re-review. Today's arithmetic says the path that gets priced on this asset isn't advertising, and I'd rather go test that path.
The story before this decision — payment processors banning the category, which is why I moved to ads at all — is in After the Payment Ban, I Went to Ads. That plan closed with today's verdict. The first source-by-source traffic numbers are in 63% of My App Traffic Came From the Channel I Wasn't Pushing.
Care to count the body text on your best-performing page right now? If it comes back smaller than you expected, you're standing where I am.